// Copyright (c) 2011 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/text_utils.h" #include #include #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/font_list.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" namespace gfx { namespace { TEST(TextUtilsTest, GetStringWidth) { FontList font_list; EXPECT_EQ(GetStringWidth(std::u16string(), font_list), 0); EXPECT_GT(GetStringWidth(u"a", font_list), GetStringWidth(std::u16string(), font_list)); EXPECT_GT(GetStringWidth(u"ab", font_list), GetStringWidth(u"a", font_list)); EXPECT_GT(GetStringWidth(u"abc", font_list), GetStringWidth(u"ab", font_list)); } TEST(TextUtilsTest, GetStringSize) { std::vector strings{ std::u16string(), u"a", u"abc", }; FontList font_list; for (std::u16string string : strings) { gfx::Size size = GetStringSize(string, font_list); EXPECT_EQ(GetStringWidth(string, font_list), size.width()) << " input string is \"" << string << "\""; EXPECT_EQ(font_list.GetHeight(), size.height()) << " input string is \"" << string << "\""; } } TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderLargerThanFont) { FontList font_list; // We will make some assumptions about the default font - specifically that it // has leading space and space for the descender. DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight()); DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight()); // Adjust a large border for the default font. Using a large number means that // the border will extend outside the leading and descender area of the font. constexpr gfx::Insets kOriginalBorder(20); const gfx::Insets result = AdjustVisualBorderForFont(font_list, kOriginalBorder); EXPECT_EQ(result.left(), kOriginalBorder.left()); EXPECT_EQ(result.right(), kOriginalBorder.right()); EXPECT_LT(result.top(), kOriginalBorder.top()); EXPECT_LT(result.bottom(), kOriginalBorder.bottom()); } TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderSmallerThanFont) { FontList font_list; // We will make some assumptions about the default font - specifically that it // has leading space and space for the descender. DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight()); DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight()); // Adjust a border with a small vertical component. The vertical component // should go to zero because it overlaps the leading and descender areas of // the font. constexpr gfx::Insets kSmallVerticalInsets(1, 20); const gfx::Insets result = AdjustVisualBorderForFont(font_list, kSmallVerticalInsets); EXPECT_EQ(result.left(), kSmallVerticalInsets.left()); EXPECT_EQ(result.right(), kSmallVerticalInsets.right()); EXPECT_EQ(result.top(), 0); EXPECT_EQ(result.bottom(), 0); } TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsSmaller) { FontList original_font; FontList smaller_font = original_font.DeriveWithSizeDelta(-3); DCHECK_LT(smaller_font.GetCapHeight(), original_font.GetCapHeight()); EXPECT_GT(GetFontCapHeightCenterOffset(original_font, smaller_font), 0); } TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsLarger) { FontList original_font; FontList larger_font = original_font.DeriveWithSizeDelta(3); DCHECK_GT(larger_font.GetCapHeight(), original_font.GetCapHeight()); EXPECT_LT(GetFontCapHeightCenterOffset(original_font, larger_font), 0); } TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SameSize) { FontList original_font; EXPECT_EQ(0, GetFontCapHeightCenterOffset(original_font, original_font)); } struct RemoveAcceleratorCharData { const char* input; int accelerated_char_pos; int accelerated_char_span; const char* output_locate_and_strip; const char* output_full_strip; const char* name; }; class RemoveAcceleratorCharTest : public testing::TestWithParam { public: static const RemoveAcceleratorCharData kCases[]; }; const RemoveAcceleratorCharData RemoveAcceleratorCharTest::kCases[] = { {"", -1, 0, "", "", "EmptyString"}, {"&", -1, 0, "", "", "AcceleratorCharOnly"}, {"no accelerator", -1, 0, "no accelerator", "no accelerator", "NoAccelerator"}, {"&one accelerator", 0, 1, "one accelerator", "one accelerator", "OneAccelerator_Start"}, {"one &accelerator", 4, 1, "one accelerator", "one accelerator", "OneAccelerator_Middle"}, {"one accelerator&", -1, 0, "one accelerator", "one accelerator", "OneAccelerator_End"}, {"&two &accelerators", 4, 1, "two accelerators", "two accelerators", "TwoAccelerators_OneAtStart"}, {"two &accelerators&", 4, 1, "two accelerators", "two accelerators", "TwoAccelerators_OneAtEnd"}, {"two& &accelerators", 4, 1, "two accelerators", "two accelerators", "TwoAccelerators_SpaceBetween"}, {"&&escaping", -1, 0, "&escaping", "&escaping", "Escape_Start"}, {"escap&&ing", -1, 0, "escap&ing", "escap&ing", "Escape_Middle"}, {"escaping&&", -1, 0, "escaping&", "escaping&", "Escape_End"}, {"accelerator(&A)", 12, 1, "accelerator(A)", "accelerator", "CJK_Style"}, {"accelerator(&A)...", 12, 1, "accelerator(A)...", "accelerator...", "CJK_StyleEllipsis"}, {"accelerator(paren", -1, 0, "accelerator(paren", "accelerator(paren", "CJK_OpenParen"}, {"accelerator(&paren", 12, 1, "accelerator(paren", "accelerator(paren", "CJK_NoClosingParen"}, {"accelerator(&paren)", 12, 1, "accelerator(paren)", "accelerator(paren)", "CJK_LateClosingParen"}, {"accelerator&paren)", 11, 1, "acceleratorparen)", "acceleratorparen)", "CJK_NoOpeningParen"}, {"accelerator(P)", -1, 0, "accelerator(P)", "accelerator(P)", "CJK_JustParens"}, {"accelerator(&)", 12, 1, "accelerator()", "accelerator()", "CJK_MissingAccelerator"}, {"&mix&&ed", 0, 1, "mix&ed", "mix&ed", "Mixed_EscapeAfterAccelerator"}, {"&&m&ix&&e&d&", 6, 1, "&mix&ed", "&mix&ed", "Mixed_MiddleAcceleratorSkipped"}, {"&&m&&ix&ed&&", 5, 1, "&m&ixed&", "&m&ixed&", "Mixed_OneAccelerator"}, {"&m&&ix&ed&&", 4, 1, "m&ixed&", "m&ixed&", "Mixed_InitialAcceleratorSkipped"}, // U+1D49C MATHEMATICAL SCRIPT CAPITAL A, which occupies two |char16|'s. {"&\U0001D49C", 0, 2, "\U0001D49C", "\U0001D49C", "MultibyteAccelerator_Start"}, {"Test&\U0001D49Cing", 4, 2, "Test\U0001D49Cing", "Test\U0001D49Cing", "MultibyteAccelerator_Middle"}, {"Test\U0001D49C&ing", 6, 1, "Test\U0001D49Cing", "Test\U0001D49Cing", "OneAccelerator_AfterMultibyte"}, {"Test&\U0001D49C&ing", 6, 1, "Test\U0001D49Cing", "Test\U0001D49Cing", "MultibyteAccelerator_Skipped"}, {"Test&\U0001D49C&&ing", 4, 2, "Test\U0001D49C&ing", "Test\U0001D49C&ing", "MultibyteAccelerator_EscapeAfter"}, {"Test&\U0001D49C&\U0001D49Cing", 6, 2, "Test\U0001D49C\U0001D49Cing", "Test\U0001D49C\U0001D49Cing", "MultibyteAccelerator_AfterMultibyteAccelerator"}, {"accelerator(&\U0001D49C)", 12, 2, "accelerator(\U0001D49C)", "accelerator", "MultibyteAccelerator_CJK"}, }; INSTANTIATE_TEST_SUITE_P( All, RemoveAcceleratorCharTest, testing::ValuesIn(RemoveAcceleratorCharTest::kCases), [](const testing::TestParamInfo& param_info) { return param_info.param.name; }); TEST_P(RemoveAcceleratorCharTest, RemoveAcceleratorChar) { RemoveAcceleratorCharData data = GetParam(); int accelerated_char_pos; int accelerated_char_span; std::u16string result_locate_and_strip = LocateAndRemoveAcceleratorChar( base::UTF8ToUTF16(data.input), &accelerated_char_pos, &accelerated_char_span); std::u16string result_full_strip = RemoveAccelerator(base::UTF8ToUTF16(data.input)); EXPECT_EQ(result_locate_and_strip, base::UTF8ToUTF16(data.output_locate_and_strip)); EXPECT_EQ(result_full_strip, base::UTF8ToUTF16(data.output_full_strip)); EXPECT_EQ(accelerated_char_pos, data.accelerated_char_pos); EXPECT_EQ(accelerated_char_span, data.accelerated_char_span); } struct FindValidBoundaryData { const char16_t* input; size_t index_in; bool trim_whitespace; size_t index_out; const char* name; }; class FindValidBoundaryBeforeTest : public testing::TestWithParam { public: static const FindValidBoundaryData kCases[]; }; const FindValidBoundaryData FindValidBoundaryBeforeTest::kCases[] = { {u"", 0, false, 0, "Empty"}, {u"word", 0, false, 0, "StartOfString"}, {u"word", 4, false, 4, "EndOfString"}, {u"word", 2, false, 2, "MiddleOfString_OnValidCharacter"}, {u"w𐐷d", 2, false, 1, "MiddleOfString_OnSurrogatePair"}, {u"w𐐷d", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"}, {u"w𐐷d", 3, false, 3, "MiddleOfString_AfterSurrogatePair"}, {u"wo d", 3, false, 3, "MiddleOfString_OnSpace_NoTrim"}, {u"wo d", 3, true, 2, "MiddleOfString_OnSpace_Trim"}, {u"wo d", 2, false, 2, "MiddleOfString_LeftOfSpace_NoTrim"}, {u"wo d", 2, true, 2, "MiddleOfString_LeftOfSpace_Trim"}, {u"wo\td", 3, false, 3, "MiddleOfString_OnTab_NoTrim"}, {u"wo\td", 3, true, 2, "MiddleOfString_OnTab_Trim"}, {u"w d", 3, false, 3, "MiddleOfString_MultipleWhitespace_NoTrim"}, {u"w d", 3, true, 1, "MiddleOfString_MultipleWhitespace_Trim"}, {u"w d", 2, false, 2, "MiddleOfString_MiddleOfWhitespace_NoTrim"}, {u"w d", 2, true, 1, "MiddleOfString_MiddleOfWhitespace_Trim"}, {u"w ", 3, false, 3, "EndOfString_Whitespace_NoTrim"}, {u"w ", 3, true, 1, "EndOfString_Whitespace_Trim"}, {u" d", 2, false, 2, "MiddleOfString_Whitespace_NoTrim"}, {u" d", 2, true, 0, "MiddleOfString_Whitespace_Trim"}, // COMBINING GRAVE ACCENT (U+0300) {u"wo\u0300d", 2, false, 1, "MiddleOfString_OnCombiningMark"}, {u"wo\u0300d", 1, false, 1, "MiddleOfString_BeforeCombiningMark"}, {u"wo\u0300d", 3, false, 3, "MiddleOfString_AfterCombiningMark"}, {u"w o\u0300d", 3, true, 1, "MiddleOfString_SpaceAndCombinginMark_Trim"}, {u"wo\u0300 d", 3, true, 3, "MiddleOfString_CombiningMarkAndSpace_Trim"}, {u"w \u0300d", 3, true, 3, "MiddleOfString_AfterSpaceWithCombiningMark_Trim"}, {u"w \u0300d", 2, true, 1, "MiddleOfString_OnSpaceWithCombiningMark_Trim"}, {u"w \u0300 d", 4, true, 3, "MiddleOfString_AfterSpaceAfterSpaceWithCombiningMark_Trim"}, // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1 // (U+1D16E) {u"w\U0001D11E\U0001D16Ed", 1, false, 1, "MiddleOfString_BeforeCombiningSurrogate"}, {u"w\U0001D11E\U0001D16Ed", 2, false, 1, "MiddleOfString_OnCombiningSurrogate_Pos1"}, {u"w\U0001D11E\U0001D16Ed", 3, false, 1, "MiddleOfString_OnCombiningSurrogate_Pos2"}, {u"w\U0001D11E\U0001D16Ed", 4, false, 1, "MiddleOfString_OnCombiningSurrogate_Pos3"}, {u"w\U0001D11E\U0001D16Ed", 5, false, 5, "MiddleOfString_AfterCombiningSurrogate"}, }; INSTANTIATE_TEST_SUITE_P( All, FindValidBoundaryBeforeTest, testing::ValuesIn(FindValidBoundaryBeforeTest::kCases), [](const testing::TestParamInfo& param_info) { return param_info.param.name; }); TEST_P(FindValidBoundaryBeforeTest, FindValidBoundaryBefore) { FindValidBoundaryData data = GetParam(); const std::u16string::const_pointer input = reinterpret_cast(data.input); DLOG(INFO) << input; size_t result = FindValidBoundaryBefore(input, data.index_in, data.trim_whitespace); EXPECT_EQ(data.index_out, result); } class FindValidBoundaryAfterTest : public testing::TestWithParam { public: static const FindValidBoundaryData kCases[]; }; const FindValidBoundaryData FindValidBoundaryAfterTest::kCases[] = { {u"", 0, false, 0, "Empty"}, {u"word", 0, false, 0, "StartOfString"}, {u"word", 4, false, 4, "EndOfString"}, {u"word", 2, false, 2, "MiddleOfString_OnValidCharacter"}, {u"w𐐷d", 2, false, 3, "MiddleOfString_OnSurrogatePair"}, {u"w𐐷d", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"}, {u"w𐐷d", 3, false, 3, "MiddleOfString_AfterSurrogatePair"}, {u"wo d", 2, false, 2, "MiddleOfString_OnSpace_NoTrim"}, {u"wo d", 2, true, 3, "MiddleOfString_OnSpace_Trim"}, {u"wo d", 3, false, 3, "MiddleOfString_RightOfSpace_NoTrim"}, {u"wo d", 3, true, 3, "MiddleOfString_RightOfSpace_Trim"}, {u"wo\td", 2, false, 2, "MiddleOfString_OnTab_NoTrim"}, {u"wo\td", 2, true, 3, "MiddleOfString_OnTab_Trim"}, {u"w d", 1, false, 1, "MiddleOfString_MultipleWhitespace_NoTrim"}, {u"w d", 1, true, 3, "MiddleOfString_MultipleWhitespace_Trim"}, {u"w d", 2, false, 2, "MiddleOfString_MiddleOfWhitespace_NoTrim"}, {u"w d", 2, true, 3, "MiddleOfString_MiddleOfWhitespace_Trim"}, {u"w ", 1, false, 1, "MiddleOfString_Whitespace_NoTrim"}, {u"w ", 1, true, 3, "MiddleOfString_Whitespace_Trim"}, {u" d", 0, false, 0, "StartOfString_Whitespace_NoTrim"}, {u" d", 0, true, 2, "StartOfString_Whitespace_Trim"}, // COMBINING GRAVE ACCENT (U+0300) {u"wo\u0300d", 2, false, 3, "MiddleOfString_OnCombiningMark"}, {u"wo\u0300d", 1, false, 1, "MiddleOfString_BeforeCombiningMark"}, {u"wo\u0300d", 3, false, 3, "MiddleOfString_AfterCombiningMark"}, {u"w o\u0300d", 1, true, 2, "MiddleOfString_SpaceAndCombinginMark_Trim"}, {u"wo\u0300 d", 1, true, 1, "MiddleOfString_BeforeCombiningMarkAndSpace_Trim"}, {u"wo\u0300 d", 2, true, 4, "MiddleOfString_OnCombiningMarkAndSpace_Trim"}, {u"w \u0300d", 1, true, 1, "MiddleOfString_BeforeSpaceWithCombiningMark_Trim"}, {u"w \u0300d", 2, true, 3, "MiddleOfString_OnSpaceWithCombiningMark_Trim"}, {u"w \u0300d", 1, true, 2, "MiddleOfString_BeforeSpaceBeforeSpaceWithCombiningMark_Trim"}, // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1 // (U+1D16E) {u"w\U0001D11E\U0001D16Ed", 1, false, 1, "MiddleOfString_BeforeCombiningSurrogate"}, {u"w\U0001D11E\U0001D16Ed", 2, false, 5, "MiddleOfString_OnCombiningSurrogate_Pos1"}, {u"w\U0001D11E\U0001D16Ed", 3, false, 5, "MiddleOfString_OnCombiningSurrogate_Pos2"}, {u"w\U0001D11E\U0001D16Ed", 4, false, 5, "MiddleOfString_OnCombiningSurrogate_Pos3"}, {u"w\U0001D11E\U0001D16Ed", 5, false, 5, "MiddleOfString_AfterCombiningSurrogate"}, }; INSTANTIATE_TEST_SUITE_P( All, FindValidBoundaryAfterTest, testing::ValuesIn(FindValidBoundaryAfterTest::kCases), [](const testing::TestParamInfo& param_info) { return param_info.param.name; }); TEST_P(FindValidBoundaryAfterTest, FindValidBoundaryAfter) { FindValidBoundaryData data = GetParam(); const std::u16string::const_pointer input = reinterpret_cast(data.input); size_t result = FindValidBoundaryAfter(input, data.index_in, data.trim_whitespace); EXPECT_EQ(data.index_out, result); } } // namespace } // namespace gfx