diff options
author | dschuyler <dschuyler@chromium.org> | 2015-03-10 18:40:11 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-11 01:40:59 +0000 |
commit | fcd7691008ae402b8bc0d678fc7530ea0aebf990 (patch) | |
tree | b8bda64b1f95da71553508736755974404a8a9d4 | |
parent | 22a55369c16d825d2f06df9be5bfeb44840ccab2 (diff) | |
download | chromium_src-fcd7691008ae402b8bc0d678fc7530ea0aebf990.zip chromium_src-fcd7691008ae402b8bc0d678fc7530ea0aebf990.tar.gz chromium_src-fcd7691008ae402b8bc0d678fc7530ea0aebf990.tar.bz2 |
Adding baseline options for super/sub scripting
This change list adds four options for a smaller font to be used as a style within the render text.
Option Example
------ -------
SUPERSCRIPT a mathematical exponent would be superscript
SUPERIOR 8th, the "th" would be superior script
INFERIOR 1/2, the "2" would be inferior ("1" is superior)
SUBSCRIPT H2O, the "2" would be subscript
Some imagination is needed to interpret the examples above. To see a clearer references to what is meant, this wikipedia article may be helpful: http://en.wikipedia.org/wiki/Subscript_and_superscript
These options may be set in the same way options like BOLD and ITALIC can currently be set, e.g. with a call to my_render_text->ApplyStyle(gfx::SUPERSCRIPT, true, gfx::Range(1, 3));
BUG=459812
Review URL: https://codereview.chromium.org/990323002
Cr-Commit-Position: refs/heads/master@{#320022}
-rw-r--r-- | ui/gfx/render_text.cc | 65 | ||||
-rw-r--r-- | ui/gfx/render_text.h | 23 | ||||
-rw-r--r-- | ui/gfx/render_text_harfbuzz.cc | 41 | ||||
-rw-r--r-- | ui/gfx/render_text_harfbuzz.h | 2 | ||||
-rw-r--r-- | ui/gfx/render_text_mac.cc | 2 | ||||
-rw-r--r-- | ui/gfx/render_text_unittest.cc | 220 | ||||
-rw-r--r-- | ui/gfx/text_constants.h | 19 |
7 files changed, 315 insertions, 57 deletions
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index 5a3541f..2f10216 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc @@ -174,6 +174,25 @@ SkPaint::Hinting FontRenderParamsHintingToSkPaintHinting( return SkPaint::kNo_Hinting; } +// Make sure ranges don't break text graphemes. If a range in |break_list| +// does break a grapheme in |render_text|, the range will be slightly +// extended to encompass the grapheme. +template <typename T> +void RestoreBreakList(RenderText* render_text, BreakList<T>& break_list) { + break_list.SetMax(render_text->text().length()); + Range range; + while (range.end() < break_list.max()) { + const auto& current_break = break_list.GetBreak(range.end()); + range = break_list.GetRange(current_break); + if (range.end() < break_list.max() && + !render_text->IsValidCursorIndex(range.end())) { + range.set_end( + render_text->IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD)); + break_list.ApplyValue(current_break->second, range); + } + } +} + } // namespace namespace internal { @@ -349,10 +368,11 @@ void SkiaTextRenderer::DiagonalStrike::Draw() { } StyleIterator::StyleIterator(const BreakList<SkColor>& colors, - const std::vector<BreakList<bool> >& styles) - : colors_(colors), - styles_(styles) { + const BreakList<BaselineStyle>& baselines, + const std::vector<BreakList<bool>>& styles) + : colors_(colors), baselines_(baselines), styles_(styles) { color_ = colors_.breaks().begin(); + baseline_ = baselines_.breaks().begin(); for (size_t i = 0; i < styles_.size(); ++i) style_.push_back(styles_[i].breaks().begin()); } @@ -361,6 +381,7 @@ StyleIterator::~StyleIterator() {} Range StyleIterator::GetRange() const { Range range(colors_.GetRange(color_)); + range = range.Intersect(baselines_.GetRange(baseline_)); for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) range = range.Intersect(styles_[i].GetRange(style_[i])); return range; @@ -368,6 +389,7 @@ Range StyleIterator::GetRange() const { void StyleIterator::UpdatePosition(size_t position) { color_ = colors_.GetBreak(position); + baseline_ = baselines_.GetBreak(position); for (size_t i = 0; i < NUM_TEXT_STYLES; ++i) style_[i] = styles_[i].GetBreak(position); } @@ -423,11 +445,13 @@ void RenderText::SetText(const base::string16& text) { return; text_ = text; - // Adjust ranged styles and colors to accommodate a new text length. - // Clear style ranges as they might break new text graphemes and apply + // Adjust ranged styles, baselines, and colors to accommodate a new text + // length. Clear style ranges as they might break new text graphemes and apply // the first style to the whole text instead. const size_t text_length = text_.length(); colors_.SetMax(text_length); + baselines_.SetValue(baselines_.breaks().begin()->second); + baselines_.SetMax(text_length); for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) { BreakList<bool>& break_list = styles_[style]; break_list.SetValue(break_list.breaks().begin()->second); @@ -670,6 +694,14 @@ void RenderText::ApplyColor(SkColor value, const Range& range) { colors_.ApplyValue(value, range); } +void RenderText::SetBaselineStyle(BaselineStyle value) { + baselines_.SetValue(value); +} + +void RenderText::ApplyBaselineStyle(BaselineStyle value, const Range& range) { + baselines_.ApplyValue(value, range); +} + void RenderText::SetStyle(TextStyle style, bool value) { styles_[style].SetValue(value); @@ -910,6 +942,7 @@ RenderText::RenderText() focused_(false), composition_range_(Range::InvalidRange()), colors_(kDefaultColor), + baselines_(NORMAL_BASELINE), styles_(NUM_TEXT_STYLES), composition_and_selection_styles_applied_(false), obscured_(false), @@ -1294,6 +1327,7 @@ base::string16 RenderText::Elide(const base::string16& text, render_text->SetCursorEnabled(cursor_enabled_); render_text->set_truncate_length(truncate_length_); render_text->styles_ = styles_; + render_text->baselines_ = baselines_; render_text->colors_ = colors_; if (text_width == 0) { render_text->SetText(text); @@ -1347,24 +1381,11 @@ base::string16 RenderText::Elide(const base::string16& text, render_text->SetText(new_text); } - // Restore styles. Make sure style ranges don't break new text graphemes. + // Restore styles and baselines without breaking multi-character graphemes. render_text->styles_ = styles_; - for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) { - BreakList<bool>& break_list = render_text->styles_[style]; - break_list.SetMax(render_text->text_.length()); - Range range; - while (range.end() < break_list.max()) { - BreakList<bool>::const_iterator current_break = - break_list.GetBreak(range.end()); - range = break_list.GetRange(current_break); - if (range.end() < break_list.max() && - !render_text->IsValidCursorIndex(range.end())) { - range.set_end(render_text->IndexOfAdjacentGrapheme(range.end(), - CURSOR_FORWARD)); - break_list.ApplyValue(current_break->second, range); - } - } - } + for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) + RestoreBreakList(render_text.get(), render_text->styles_[style]); + RestoreBreakList(render_text.get(), baselines_); // We check the width of the whole desired string at once to ensure we // handle kerning/ligatures/etc. correctly. diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h index c7dec6f..4abb7c8 100644 --- a/ui/gfx/render_text.h +++ b/ui/gfx/render_text.h @@ -109,15 +109,17 @@ class GFX_EXPORT SkiaTextRenderer { DISALLOW_COPY_AND_ASSIGN(SkiaTextRenderer); }; -// Internal helper class used by derived classes to iterate colors and styles. +// Internal helper class used to iterate colors, baselines, and styles. class StyleIterator { public: StyleIterator(const BreakList<SkColor>& colors, - const std::vector<BreakList<bool> >& styles); + const BreakList<BaselineStyle>& baselines, + const std::vector<BreakList<bool>>& styles); ~StyleIterator(); // Get the colors and styles at the current iterator position. SkColor color() const { return color_->second; } + BaselineStyle baseline() const { return baseline_->second; } bool style(TextStyle s) const { return style_[s]->second; } // Get the intersecting range of the current iterator set. @@ -128,9 +130,11 @@ class StyleIterator { private: BreakList<SkColor> colors_; + BreakList<BaselineStyle> baselines_; std::vector<BreakList<bool> > styles_; BreakList<SkColor>::const_iterator color_; + BreakList<BaselineStyle>::const_iterator baseline_; std::vector<BreakList<bool>::const_iterator> style_; DISALLOW_COPY_AND_ASSIGN(StyleIterator); @@ -332,6 +336,11 @@ class GFX_EXPORT RenderText { void SetColor(SkColor value); void ApplyColor(SkColor value, const Range& range); + // Set the baseline style over the entire text or a logical character range. + // The |range| should be valid, non-reversed, and within [0, text().length()]. + void SetBaselineStyle(BaselineStyle value); + void ApplyBaselineStyle(BaselineStyle value, const Range& range); + // 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()]. @@ -456,6 +465,7 @@ class GFX_EXPORT RenderText { bool text_elided() const { return text_elided_; } const BreakList<SkColor>& colors() const { return colors_; } + const BreakList<BaselineStyle>& baselines() const { return baselines_; } const std::vector<BreakList<bool> >& styles() const { return styles_; } const std::vector<internal::Line>& lines() const { return lines_; } @@ -595,9 +605,9 @@ class GFX_EXPORT RenderText { private: friend class RenderTextTest; - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, DefaultStyle); - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, SetColorAndStyle); - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyColorAndStyle); + FRIEND_TEST_ALL_PREFIXES(RenderTextTest, DefaultStyles); + FRIEND_TEST_ALL_PREFIXES(RenderTextTest, SetStyles); + FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyles); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ObscuredText); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, RevealObscuredText); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ElidedText); @@ -698,10 +708,11 @@ class GFX_EXPORT RenderText { // Composition text range. Range composition_range_; - // Color and style breaks, used to color and stylize ranges of text. + // Color, baseline, and style breaks, used to modify ranges of text. // BreakList positions are stored with text indices, not display indices. // TODO(msw): Expand to support cursor, selection, background, etc. colors. BreakList<SkColor> colors_; + BreakList<BaselineStyle> baselines_; std::vector<BreakList<bool> > styles_; // Breaks saved without temporary composition and selection styling. diff --git a/ui/gfx/render_text_harfbuzz.cc b/ui/gfx/render_text_harfbuzz.cc index 72caa59..916cdf3 100644 --- a/ui/gfx/render_text_harfbuzz.cc +++ b/ui/gfx/render_text_harfbuzz.cc @@ -19,6 +19,7 @@ #include "ui/gfx/canvas.h" #include "ui/gfx/font_fallback.h" #include "ui/gfx/font_render_params.h" +#include "ui/gfx/geometry/safe_integer_conversions.h" #include "ui/gfx/harfbuzz_font_skia.h" #include "ui/gfx/range/range_f.h" #include "ui/gfx/utf16_indexing.h" @@ -421,6 +422,7 @@ class HarfBuzzLineBreaker { paint.getFontMetrics(&metrics); line->size.set_width(line->size.width() + width); + // TODO(dschuyler): Account for stylized baselines in string sizing. max_descent_ = std::max(max_descent_, metrics.fDescent); // fAscent is always negative. max_ascent_ = std::max(max_ascent_, -metrics.fAscent); @@ -481,10 +483,13 @@ TextRunHarfBuzz::TextRunHarfBuzz() script(USCRIPT_INVALID_CODE), glyph_count(static_cast<size_t>(-1)), font_size(0), + baseline_offset(0), + baseline_type(0), font_style(0), strike(false), diagonal_strike(false), - underline(false) {} + underline(false) { +} TextRunHarfBuzz::~TextRunHarfBuzz() {} @@ -1030,7 +1035,7 @@ void RenderTextHarfBuzz::DrawVisualTextInternal( (glyphs_range.start() - j) : (glyphs_range.start() + j)]; positions[j].offset(SkIntToScalar(origin.x()) + offset_x, - SkIntToScalar(origin.y())); + SkIntToScalar(origin.y() + run.baseline_offset)); } for (BreakList<SkColor>::const_iterator it = colors().GetBreak(segment.char_range.start()); @@ -1139,17 +1144,18 @@ void RenderTextHarfBuzz::ItemizeTextToRuns( // Temporarily apply composition underlines and selection colors. ApplyCompositionAndSelectionStyles(); - // Build the list of runs from the script items and ranged styles. Use an - // empty color BreakList to avoid breaking runs at color boundaries. + // Build the run list from the script items and ranged styles and baselines. + // Use an empty color BreakList to avoid breaking runs at color boundaries. BreakList<SkColor> empty_colors; empty_colors.SetMax(text.length()); - internal::StyleIterator style(empty_colors, styles()); + internal::StyleIterator style(empty_colors, baselines(), styles()); for (size_t run_break = 0; run_break < text.length();) { internal::TextRunHarfBuzz* run = new internal::TextRunHarfBuzz; run->range.set_start(run_break); run->font_style = (style.style(BOLD) ? Font::BOLD : 0) | (style.style(ITALIC) ? Font::ITALIC : 0); + run->baseline_type = style.baseline(); run->strike = style.style(STRIKE); run->diagonal_strike = style.style(DIAGONAL_STRIKE); run->underline = style.style(UNDERLINE); @@ -1218,6 +1224,31 @@ void RenderTextHarfBuzz::ShapeRun(const base::string16& text, const Font& primary_font = font_list().GetPrimaryFont(); const std::string primary_family = primary_font.GetFontName(); run->font_size = primary_font.GetFontSize(); + run->baseline_offset = 0; + if (run->baseline_type != NORMAL_BASELINE) { + // Calculate a slightly smaller font. The ratio here is somewhat arbitrary. + // Proportions from 5/9 to 5/7 all look pretty good. + const float ratio = 5.0f / 9.0f; + run->font_size = gfx::ToRoundedInt(primary_font.GetFontSize() * ratio); + switch (run->baseline_type) { + case SUPERSCRIPT: + run->baseline_offset = + primary_font.GetCapHeight() - primary_font.GetHeight(); + break; + case SUPERIOR: + run->baseline_offset = + gfx::ToRoundedInt(primary_font.GetCapHeight() * ratio) - + primary_font.GetCapHeight(); + break; + case SUBSCRIPT: + run->baseline_offset = + primary_font.GetHeight() - primary_font.GetBaseline(); + break; + case INFERIOR: // Fall through. + default: + break; + } + } std::string best_family; FontRenderParams best_render_params; diff --git a/ui/gfx/render_text_harfbuzz.h b/ui/gfx/render_text_harfbuzz.h index ac96d33..6d78dfa 100644 --- a/ui/gfx/render_text_harfbuzz.h +++ b/ui/gfx/render_text_harfbuzz.h @@ -70,6 +70,8 @@ struct GFX_EXPORT TextRunHarfBuzz { skia::RefPtr<SkTypeface> skia_face; FontRenderParams render_params; int font_size; + int baseline_offset; + int baseline_type; int font_style; bool strike; bool diagonal_strike; diff --git a/ui/gfx/render_text_mac.cc b/ui/gfx/render_text_mac.cc index 4024c4a..3c60460 100644 --- a/ui/gfx/render_text_mac.cc +++ b/ui/gfx/render_text_mac.cc @@ -251,7 +251,7 @@ base::ScopedCFTypeRef<CFMutableArrayRef> RenderTextMac::ApplyStyles( CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html - internal::StyleIterator style(colors(), styles()); + internal::StyleIterator style(colors(), baselines(), styles()); const size_t layout_text_length = CFAttributedStringGetLength(attr_string); for (size_t i = 0, end = 0; i < layout_text_length; i = end) { end = TextIndexToGivenTextIndex(text, style.GetRange().end()); diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc index 8009305..fa544f3 100644 --- a/ui/gfx/render_text_unittest.cc +++ b/ui/gfx/render_text_unittest.cc @@ -169,34 +169,80 @@ class TestSkiaTextRenderer : public internal::SkiaTextRenderer { DISALLOW_COPY_AND_ASSIGN(TestSkiaTextRenderer); }; +// Given a buffer to test against, this can be used to test various areas of the +// rectangular buffer against a specific color value. +class TestRectangleBuffer { + public: + TestRectangleBuffer(const wchar_t* string, + const SkColor* buffer, + uint32_t stride, + uint32_t row_count) + : string_(string), + buffer_(buffer), + stride_(stride), + row_count_(row_count) {} + + // Test if any values in the rectangular area are anything other than |color|. + void EnsureSolidRect(SkColor color, + int left, + int top, + int width, + int height) const { + ASSERT_LT(top, row_count_) << string_; + ASSERT_LE(top + height, row_count_) << string_; + ASSERT_LT(left, stride_) << string_; + ASSERT_LE(left + width, stride_) << string_ << ", left " << left + << ", width " << width << ", stride_ " + << stride_; + for (int y = top; y < top + height; ++y) { + for (int x = left; x < left + width; ++x) { + SkColor buffer_color = buffer_[x + y * stride_]; + EXPECT_EQ(color, buffer_color) << string_ << " at " << x << ", " << y; + } + } + } + + private: + const wchar_t* string_; + const SkColor* buffer_; + int stride_; + int row_count_; + + DISALLOW_COPY_AND_ASSIGN(TestRectangleBuffer); +}; + } // namespace class RenderTextTest : public testing::Test { }; -TEST_F(RenderTextTest, DefaultStyle) { +TEST_F(RenderTextTest, DefaultStyles) { // Check the default styles applied to new instances and adjusted text. scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); EXPECT_TRUE(render_text->text().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)); + EXPECT_TRUE( + render_text->baselines().EqualsValueForTesting(NORMAL_BASELINE)); 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, SetColorAndStyle) { +TEST_F(RenderTextTest, SetStyles) { // Ensure custom default styles persist across setting and clearing text. scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); const SkColor color = SK_ColorRED; render_text->SetColor(color); + render_text->SetBaselineStyle(SUPERSCRIPT); 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->baselines().EqualsValueForTesting(SUPERSCRIPT)); EXPECT_TRUE(render_text->styles()[BOLD].EqualsValueForTesting(true)); EXPECT_TRUE(render_text->styles()[UNDERLINE].EqualsValueForTesting(false)); render_text->SetText(WideToUTF16(cases[i])); @@ -209,38 +255,55 @@ TEST_F(RenderTextTest, SetColorAndStyle) { } } -TEST_F(RenderTextTest, ApplyColorAndStyle) { +TEST_F(RenderTextTest, ApplyStyles) { 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, Range(1, 4)); + render_text->ApplyBaselineStyle(SUPERIOR, Range(2, 4)); render_text->ApplyStyle(BOLD, true, 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, BaselineStyle>> expected_baseline_style; + expected_baseline_style.push_back( + std::pair<size_t, BaselineStyle>(0, NORMAL_BASELINE)); + expected_baseline_style.push_back( + std::pair<size_t, BaselineStyle>(2, SUPERIOR)); + expected_baseline_style.push_back( + std::pair<size_t, BaselineStyle>(4, NORMAL_BASELINE)); + EXPECT_TRUE( + render_text->baselines().EqualsForTesting(expected_baseline_style)); 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. + // Ensure that setting a value overrides the ranged values. render_text->SetColor(SK_ColorBLUE); EXPECT_TRUE(render_text->colors().EqualsValueForTesting(SK_ColorBLUE)); + render_text->SetBaselineStyle(SUBSCRIPT); + EXPECT_TRUE(render_text->baselines().EqualsValueForTesting(SUBSCRIPT)); 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) + // Apply a value 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, Range(0, text_length)); + render_text->ApplyBaselineStyle(SUPERIOR, Range(0, text_length)); render_text->ApplyStyle(BOLD, true, 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, BaselineStyle>> expected_baseline_end; + expected_baseline_end.push_back( + std::pair<size_t, BaselineStyle>(0, SUPERIOR)); + EXPECT_TRUE(render_text->baselines().EqualsForTesting(expected_baseline_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)); @@ -2594,40 +2657,151 @@ TEST_F(RenderTextTest, HarfBuzz_UnicodeFallback) { #endif // defined(OS_WIN) || defined(OS_MACOSX) // Ensure that the width reported by RenderText is sufficient for drawing. Draws -// to a canvas and checks whether any pixel beyond the width is colored. +// to a canvas and checks if any pixel beyond the bounding rectangle is colored. TEST_F(RenderTextTest, TextDoesntClip) { - const wchar_t* kTestStrings[] = { L"Save", L"Remove", L"TEST", L"W", L"WWW" }; + const wchar_t* kTestStrings[] = { + L" ", + // TODO(dschuyler): Underscores draw outside GetStringSize; + // crbug.com/459812. This appears to be a preexisting issue that wasn't + // revealed by the prior unit tests. + // L"TEST_______", + L"TEST some stuff", + L"WWWWWWWWWW", + L"gAXAXAXAXAXAXA", + // TODO(dschuyler): A-Ring draws outside GetStringSize; crbug.com/459812. + // L"g\x00C5X\x00C5X\x00C5X\x00C5X\x00C5X\x00C5X\x00C5", + L"\x0647\x0654\x0647\x0654\x0647\x0654\x0647\x0654\x0645\x0631\x062D" + L"\x0628\x0627"}; const Size kCanvasSize(300, 50); - const int kTestWidth = 10; + const int kTestSize = 10; skia::RefPtr<SkSurface> surface = skia::AdoptRef( SkSurface::NewRasterN32Premul(kCanvasSize.width(), kCanvasSize.height())); scoped_ptr<Canvas> canvas( Canvas::CreateCanvasWithoutScaling(surface->getCanvas(), 1.0f)); scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); - render_text->SetDisplayRect(Rect(kCanvasSize)); - render_text->SetHorizontalAlignment(gfx::ALIGN_LEFT); + render_text->SetHorizontalAlignment(ALIGN_LEFT); render_text->SetColor(SK_ColorBLACK); for (auto string : kTestStrings) { surface->getCanvas()->clear(SK_ColorWHITE); render_text->SetText(WideToUTF16(string)); + const Size string_size = render_text->GetStringSize(); + render_text->ApplyBaselineStyle(SUPERSCRIPT, Range(1, 2)); + render_text->ApplyBaselineStyle(SUPERIOR, Range(3, 4)); + render_text->ApplyBaselineStyle(INFERIOR, Range(5, 6)); + render_text->ApplyBaselineStyle(SUBSCRIPT, Range(7, 8)); render_text->SetStyle(BOLD, true); + render_text->SetDisplayRect( + Rect(kTestSize, kTestSize, string_size.width(), string_size.height())); + // Allow the RenderText to paint outside of its display rect. + render_text->set_clip_to_display_rect(false); + ASSERT_LE(string_size.width() + kTestSize * 2, kCanvasSize.width()); + render_text->Draw(canvas.get()); - int width = render_text->GetStringSize().width(); - ASSERT_LT(width + kTestWidth, kCanvasSize.width()); - const uint32* buffer = static_cast<const uint32*>( - surface->peekPixels(NULL, NULL)); + ASSERT_LT(string_size.width() + kTestSize, kCanvasSize.width()); + const uint32* buffer = + static_cast<const uint32*>(surface->peekPixels(nullptr, nullptr)); ASSERT_NE(nullptr, buffer); + TestRectangleBuffer rect_buffer(string, buffer, kCanvasSize.width(), + kCanvasSize.height()); + { +#if !defined(OS_CHROMEOS) + // TODO(dschuyler): On ChromeOS text draws above the GetStringSize rect. + SCOPED_TRACE("TextDoesntClip Top Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, 0, kCanvasSize.width(), + kTestSize); +#endif + } + { + SCOPED_TRACE("TextDoesntClip Bottom Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, + kTestSize + string_size.height(), + kCanvasSize.width(), kTestSize); + } + { + SCOPED_TRACE("TextDoesntClip Left Side"); +#if defined(OS_WIN) + // TODO(dschuyler): On Windows XP the Unicode test draws to the left edge + // as if it is ignoring the SetDisplayRect shift by kTestSize. This + // appears to be a preexisting issue that wasn't revealed by the prior + // unit tests. +#elif defined(OS_MACOSX) + // TODO(dschuyler): On Windows (non-XP) and Mac smoothing draws left of + // text. This appears to be a preexisting issue that wasn't revealed by + // the prior unit tests. + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, kTestSize, kTestSize - 1, + string_size.height()); +#else + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, kTestSize, kTestSize, + string_size.height()); +#endif + } + { + SCOPED_TRACE("TextDoesntClip Right Side"); +#if !defined(OS_MACOSX) + // TODO(dschuyler): On Mac text draws to right of GetStringSize. This + // appears to be a preexisting issue that wasn't revealed by the prior + // unit tests. + rect_buffer.EnsureSolidRect(SK_ColorWHITE, + kTestSize + string_size.width(), kTestSize, + kTestSize, string_size.height()); +#endif + } + } +} - for (int y = 0; y < kCanvasSize.height(); ++y) { - // Allow one column of anti-aliased pixels past the expected width. - SkColor color = buffer[width + y * kCanvasSize.width()]; - EXPECT_LT(220U, color_utils::GetLuminanceForColor(color)) << string; - for (int x = 1; x < kTestWidth; ++x) { - color = buffer[width + x + y * kCanvasSize.width()]; - EXPECT_EQ(SK_ColorWHITE, color) << string; - } +// Ensure that the text will clip to the display rect. Draws to a canvas and +// checks whether any pixel beyond the bounding rectangle is colored. +TEST_F(RenderTextTest, TextDoesClip) { + const wchar_t* kTestStrings[] = {L"TEST", L"W", L"WWWW", L"gAXAXWWWW"}; + const Size kCanvasSize(300, 50); + const int kTestSize = 10; + + skia::RefPtr<SkSurface> surface = skia::AdoptRef( + SkSurface::NewRasterN32Premul(kCanvasSize.width(), kCanvasSize.height())); + scoped_ptr<Canvas> canvas( + Canvas::CreateCanvasWithoutScaling(surface->getCanvas(), 1.0f)); + scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); + render_text->SetHorizontalAlignment(ALIGN_LEFT); + render_text->SetColor(SK_ColorBLACK); + + for (auto string : kTestStrings) { + surface->getCanvas()->clear(SK_ColorWHITE); + render_text->SetText(WideToUTF16(string)); + const Size string_size = render_text->GetStringSize(); + int fake_width = string_size.width() / 2; + int fake_height = string_size.height() / 2; + render_text->SetDisplayRect( + Rect(kTestSize, kTestSize, fake_width, fake_height)); + render_text->set_clip_to_display_rect(true); + render_text->Draw(canvas.get()); + ASSERT_LT(string_size.width() + kTestSize, kCanvasSize.width()); + const uint32* buffer = + static_cast<const uint32*>(surface->peekPixels(nullptr, nullptr)); + ASSERT_NE(nullptr, buffer); + TestRectangleBuffer rect_buffer(string, buffer, kCanvasSize.width(), + kCanvasSize.height()); + { + SCOPED_TRACE("TextDoesClip Top Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, 0, kCanvasSize.width(), + kTestSize); + } + + { + SCOPED_TRACE("TextDoesClip Bottom Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, kTestSize + fake_height, + kCanvasSize.width(), kTestSize); + } + { + SCOPED_TRACE("TextDoesClip Left Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, 0, kTestSize, kTestSize, + fake_height); + } + { + SCOPED_TRACE("TextDoesClip Right Side"); + rect_buffer.EnsureSolidRect(SK_ColorWHITE, kTestSize + fake_width, + kTestSize, kTestSize, fake_height); } } } diff --git a/ui/gfx/text_constants.h b/ui/gfx/text_constants.h index 82f2a3b..c76adfa 100644 --- a/ui/gfx/text_constants.h +++ b/ui/gfx/text_constants.h @@ -42,6 +42,25 @@ enum TextStyle { NUM_TEXT_STYLES, }; +// Text baseline offset types. +// Figure of font metrics: +// +--------+--------+------------------------+-------------+ +// | | | internal leading | SUPERSCRIPT | +// | | +------------+-----------| | +// | | ascent | | SUPERIOR |-------------+ +// | height | | cap height |-----------| +// | | | | INFERIOR |-------------+ +// | |--------+------------+-----------| | +// | | descent | SUBSCRIPT | +// +--------+---------------------------------+-------------+ +enum BaselineStyle { + NORMAL_BASELINE = 0, + SUPERSCRIPT, // e.g. a mathematical exponent would be superscript. + SUPERIOR, // e.g. 8th, the "th" would be superior script. + INFERIOR, // e.g. 1/2, the "2" would be inferior ("1" is superior). + SUBSCRIPT, // e.g. H2O, the "2" would be subscript. +}; + // Elision behaviors of text that exceeds constrained dimensions. enum ElideBehavior { NO_ELIDE = 0, // Do not modify the text, it may overflow its available bounds. |