summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordschuyler <dschuyler@chromium.org>2015-03-10 18:40:11 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-11 01:40:59 +0000
commitfcd7691008ae402b8bc0d678fc7530ea0aebf990 (patch)
treeb8bda64b1f95da71553508736755974404a8a9d4
parent22a55369c16d825d2f06df9be5bfeb44840ccab2 (diff)
downloadchromium_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.cc65
-rw-r--r--ui/gfx/render_text.h23
-rw-r--r--ui/gfx/render_text_harfbuzz.cc41
-rw-r--r--ui/gfx/render_text_harfbuzz.h2
-rw-r--r--ui/gfx/render_text_mac.cc2
-rw-r--r--ui/gfx/render_text_unittest.cc220
-rw-r--r--ui/gfx/text_constants.h19
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.