Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(338)

Unified Diff: ui/gfx/render_text_unittest.cc

Issue 2942843002: RenderText: always break runs at whitespace (Closed)
Patch Set: Helper method comments Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ui/gfx/render_text_harfbuzz.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ui/gfx/render_text_unittest.cc
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index a3fef5cabc2c623af3da03340770de7fbefd1a26..2ec7e6981c7c824e173d43338b48d0e08d8ce9ff 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -305,6 +305,29 @@ base::string16 GetObscuredString(size_t length) {
return base::string16(length, RenderText::kPasswordReplacementChar);
}
+// Converts a vector of UTF8 literals into a vector of (UTF16) string16.
+std::vector<base::string16> ToString16Vec(
+ const std::vector<const char*>& utf8_literals) {
+ std::vector<base::string16> vec;
+ for (auto* const literal : utf8_literals)
+ vec.push_back(UTF8ToUTF16(literal));
+ return vec;
+}
+
+// Returns the combined character range from all text runs on |line|.
+Range LineCharRange(const internal::Line& line) {
+ if (line.segments.empty())
+ return Range();
+ Range ltr(line.segments.front().char_range.start(),
+ line.segments.back().char_range.end());
+ if (ltr.end() > ltr.start())
+ return ltr;
+
+ // For RTL, the order of segments is reversed, but the ranges are not.
+ return Range(line.segments.back().char_range.start(),
+ line.segments.front().char_range.end());
+}
+
// The class which records the drawing operations so that the test case can
// verify where exactly the glyphs are drawn.
class TestSkiaTextRenderer : public internal::SkiaTextRenderer {
@@ -462,6 +485,26 @@ class RenderTextTest : public testing::Test,
return test_api_->GetHarfBuzzRunList();
}
+ // Returns a vector of text fragments corresponding to the current list of
+ // text runs.
+ std::vector<base::string16> GetRuns() {
+ std::vector<base::string16> runs_as_text;
+ const std::vector<RenderText::FontSpan> spans =
+ render_text_->GetFontSpansForTesting();
+ for (const auto& span : spans) {
+ runs_as_text.push_back(render_text_->text().substr(span.second.GetMin(),
+ span.second.length()));
+ }
+ return runs_as_text;
+ }
+
+ // Sets the text to |text|, then returns GetRuns().
+ std::vector<base::string16> RunsFor(const base::string16& text) {
+ render_text_->SetText(text);
+ test_api()->EnsureLayout();
+ return GetRuns();
+ }
+
void ResetRenderTextInstance() {
render_text_ = CreateRenderTextInstance();
test_api_.reset(new test::RenderTextTestApi(GetRenderText()));
@@ -2922,6 +2965,71 @@ TEST_P(RenderTextTest, SelectionKeepsLigatures) {
}
}
+// Test that characters commonly used in the context of several scripts do not
+// cause text runs to break. For example the Japanese "long sound symbol" --
+// normally only used in a Katakana script, is also used on occasion when in
+// Hiragana scripts. It shouldn't cause a Hiragana text run break since that
+// could upset kerning.
+TEST_P(RenderTextTest, ScriptExtensionsDoNotBreak) {
+ // Apparently ramen restaurants prefer "らーめん" over "らあめん". The "dash"
+ // is the long sound symbol and usually just appears in Katakana writing.
+ const base::string16 ramen_hiragana = UTF8ToUTF16("らーめん");
+ const base::string16 ramen_katakana = UTF8ToUTF16("ラーメン");
+ const base::string16 ramen_mixed = UTF8ToUTF16("らあメン");
+
+ EXPECT_EQ(std::vector<base::string16>({ramen_hiragana}),
+ RunsFor(ramen_hiragana));
+ EXPECT_EQ(std::vector<base::string16>({ramen_katakana}),
+ RunsFor(ramen_katakana));
+
+ // Currently Harfbuzz breaks this, but not Mac.
+ if (GetParam() == RENDER_TEXT_HARFBUZZ)
+ EXPECT_EQ(ToString16Vec({"らあ", "メン"}), RunsFor(ramen_mixed));
+ else
+ EXPECT_EQ(std::vector<base::string16>({ramen_mixed}), RunsFor(ramen_mixed));
+}
+
+// Test that whitespace breaks runs of text. E.g. this can permit better fonts
+// to be chosen by the fallback mechanism when a font does not provide
+// whitespace glyphs for all scripts. See http://crbug.com/731563.
+TEST_P(RenderTextTest, WhitespaceDoesBreak) {
+ // Title of the Wikipedia page for "bit". ASCII spaces. In Hebrew and English.
+ // Note that the hyphens that Wikipedia uses are different. English uses
+ // ASCII (U+002D) "hyphen minus", Hebrew uses the U+2013 "EN Dash".
+ const base::string16 ascii_space_he = UTF8ToUTF16("סיבית – ויקיפדיה");
+ const base::string16 ascii_space_en = ASCIIToUTF16("Bit - Wikipedia");
+
+ // This says "thank you very much" with a full-width non-ascii space (U+3000).
+ const base::string16 full_width_space = UTF8ToUTF16("ども ありがと");
+
+ if (GetParam() == RENDER_TEXT_HARFBUZZ) {
+ // Old behavior:
+ // { "סיבית ", "–", " ", "ויקיפדיה"" }
+ // { "Bit ", "- ", "Wikipedia" }
+ // { "ども ありがと"" }.
+ EXPECT_EQ(ToString16Vec({"סיבית", " ", "–", " ", "ויקיפדיה"}),
+ RunsFor(ascii_space_he));
+ EXPECT_EQ(ToString16Vec({"Bit", " - ", "Wikipedia"}),
+ RunsFor(ascii_space_en));
+ EXPECT_EQ(ToString16Vec({"ども", " ", "ありがと"}),
+ RunsFor(full_width_space));
+ } else {
+ // Mac is a black box. From this it seems to draw RTL runs right to left and
+ // is able to use typeface characteristics when breaking runs. On 10.9, the
+ // Hebrew string is given a single run, and on 10.12 it's given 3 runs. It
+ // doesn't really matter as far as the rest of the RenderText machinery is
+ // concerned.
+ auto actual = RunsFor(ascii_space_he);
+ if (actual.size() == 3) {
+ EXPECT_EQ(ToString16Vec({"ויקיפדיה", " – ", "סיבית"}), actual);
+ } else {
+ EXPECT_EQ(ToString16Vec({"סיבית – ויקיפדיה"}), actual);
+ }
+ EXPECT_EQ(ToString16Vec({"Bit - Wikipedia"}), RunsFor(ascii_space_en));
+ EXPECT_EQ(ToString16Vec({"ども ありがと"}), RunsFor(full_width_space));
+ }
+}
+
// Ensure strings wrap onto multiple lines for a small available width.
TEST_P(RenderTextHarfBuzzTest, Multiline_MinWidth) {
const wchar_t* kTestStrings[] = { kWeak, kLtr, kLtrRtl, kLtrRtlLtr, kRtl,
@@ -2942,20 +3050,39 @@ TEST_P(RenderTextHarfBuzzTest, Multiline_MinWidth) {
// Ensure strings wrap onto multiple lines for a normal available width.
TEST_P(RenderTextHarfBuzzTest, Multiline_NormalWidth) {
+ // Should RenderText suppress drawing whitespace at the end of a line?
+ // Currently it does not.
const struct {
const wchar_t* const text;
const Range first_line_char_range;
const Range second_line_char_range;
+
+ // Lengths of each text run. Runs break at whitespace.
+ std::vector<size_t> run_lengths;
+
+ // The index of the text run that should start the second line.
+ int second_line_run_index;
+
bool is_ltr;
} kTestStrings[] = {
- { L"abc defg hijkl", Range(0, 9), Range(9, 14), true },
- { L"qwertyzxcvbn", Range(0, 10), Range(10, 12), true },
- { L"\x0627\x0644\x0644\x063A\x0629 "
- L"\x0627\x0644\x0639\x0631\x0628\x064A\x0629",
- Range(0, 6), Range(6, 13), false },
- { L"\x062A\x0641\x0627\x062D \x05EA\x05E4\x05D5\x05D6\x05D9"
- L"\x05DA\x05DB\x05DD", Range(0, 5), Range(5, 13), false }
- };
+ {L"abc defg hijkl", Range(0, 9), Range(9, 14), {3, 1, 4, 1, 5}, 4, true},
+ {L"qwertyzxcvbn", Range(0, 10), Range(10, 12), {10, 2}, 1, true},
+ // RTL: should render left-to-right as "<space>43210 \n cba9876".
+ {L"\x0627\x0644\x0644\x063A\x0629 "
+ L"\x0627\x0644\x0639\x0631\x0628\x064A\x0629",
+ Range(0, 6),
+ Range(6, 13),
+ {1 /* space first */, 5, 7},
+ 2,
+ false},
+ // RTL: should render left-to-right as "<space>3210 \n cba98765".
+ {L"\x062A\x0641\x0627\x062D \x05EA\x05E4\x05D5\x05D6\x05D9"
+ L"\x05DA\x05DB\x05DD",
+ Range(0, 5),
+ Range(5, 13),
+ {1 /* space first */, 5, 8},
+ 2,
+ false}};
RenderTextHarfBuzz* render_text = GetRenderTextHarfBuzz();
@@ -2973,31 +3100,31 @@ TEST_P(RenderTextHarfBuzzTest, Multiline_NormalWidth) {
DrawVisualText();
ASSERT_EQ(2U, test_api()->lines().size());
- ASSERT_EQ(1U, test_api()->lines()[0].segments.size());
EXPECT_EQ(kTestStrings[i].first_line_char_range,
- test_api()->lines()[0].segments[0].char_range);
- ASSERT_EQ(1U, test_api()->lines()[1].segments.size());
+ LineCharRange(test_api()->lines()[0]));
EXPECT_EQ(kTestStrings[i].second_line_char_range,
- test_api()->lines()[1].segments[0].char_range);
+ LineCharRange(test_api()->lines()[1]));
std::vector<TestSkiaTextRenderer::TextLog> text_log;
renderer()->GetTextLogAndReset(&text_log);
- ASSERT_EQ(2U, text_log.size());
+
+ ASSERT_EQ(kTestStrings[i].run_lengths.size(), text_log.size());
+
// NOTE: this expectation compares the character length and glyph counts,
// which isn't always equal. This is okay only because all the test
// strings are simple (like, no compound characters nor UTF16-surrogate
// pairs). Be careful in case more complicated test strings are added.
- EXPECT_EQ(kTestStrings[i].first_line_char_range.length(),
- text_log[0].glyph_count);
- EXPECT_EQ(kTestStrings[i].second_line_char_range.length(),
- text_log[1].glyph_count);
- EXPECT_LT(text_log[0].origin.y(), text_log[1].origin.y());
+ EXPECT_EQ(kTestStrings[i].run_lengths[0], text_log[0].glyph_count);
+ const int second_line_start = kTestStrings[i].second_line_run_index;
+ EXPECT_EQ(kTestStrings[i].run_lengths[second_line_start],
+ text_log[second_line_start].glyph_count);
+ EXPECT_LT(text_log[0].origin.y(), text_log[second_line_start].origin.y());
if (kTestStrings[i].is_ltr) {
EXPECT_EQ(0, text_log[0].origin.x());
- EXPECT_EQ(0, text_log[1].origin.x());
+ EXPECT_EQ(0, text_log[second_line_start].origin.x());
} else {
EXPECT_LT(0, text_log[0].origin.x());
- EXPECT_LT(0, text_log[1].origin.x());
+ EXPECT_LT(0, text_log[second_line_start].origin.x());
}
}
}
@@ -3184,7 +3311,7 @@ TEST_P(RenderTextHarfBuzzTest, Multiline_WordWrapBehavior) {
for (size_t j = 0; j < test_api()->lines().size(); ++j) {
SCOPED_TRACE(base::StringPrintf("%" PRIuS "-th line", j));
EXPECT_EQ(kTestScenarios[i].char_ranges[j],
- test_api()->lines()[j].segments[0].char_range);
+ LineCharRange(test_api()->lines()[j]));
EXPECT_EQ(kTestScenarios[i].char_ranges[j].length() * kGlyphSize,
test_api()->lines()[j].size.width());
}
@@ -3593,6 +3720,7 @@ TEST_P(RenderTextHarfBuzzTest, HarfBuzz_BreakRunsByUnicodeBlocks) {
render_text->SetText(WideToUTF16(L"x\x25B6y"));
test_api()->EnsureLayout();
internal::TextRunList* run_list = GetHarfBuzzRunList();
+ EXPECT_EQ(ToString16Vec({"x", "▶", "y"}), GetRuns());
ASSERT_EQ(3U, run_list->size());
EXPECT_EQ(Range(0, 1), run_list->runs()[0]->range);
EXPECT_EQ(Range(1, 2), run_list->runs()[1]->range);
@@ -3601,11 +3729,13 @@ TEST_P(RenderTextHarfBuzzTest, HarfBuzz_BreakRunsByUnicodeBlocks) {
render_text->SetText(WideToUTF16(L"x \x25B6 y"));
test_api()->EnsureLayout();
run_list = GetHarfBuzzRunList();
- ASSERT_EQ(4U, run_list->size());
- EXPECT_EQ(Range(0, 2), run_list->runs()[0]->range);
- EXPECT_EQ(Range(2, 3), run_list->runs()[1]->range);
- EXPECT_EQ(Range(3, 4), run_list->runs()[2]->range);
- EXPECT_EQ(Range(4, 5), run_list->runs()[3]->range);
+ EXPECT_EQ(ToString16Vec({"x", " ", "▶", " ", "y"}), GetRuns());
+ ASSERT_EQ(5U, run_list->size());
+ EXPECT_EQ(Range(0, 1), run_list->runs()[0]->range);
+ EXPECT_EQ(Range(1, 2), run_list->runs()[1]->range);
+ EXPECT_EQ(Range(2, 3), run_list->runs()[2]->range);
+ EXPECT_EQ(Range(3, 4), run_list->runs()[3]->range);
+ EXPECT_EQ(Range(4, 5), run_list->runs()[4]->range);
}
TEST_P(RenderTextHarfBuzzTest, HarfBuzz_BreakRunsByEmoji) {
« no previous file with comments | « ui/gfx/render_text_harfbuzz.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698