1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/gfx/text_utils.h"
6 
7 #include <stddef.h>
8 
9 #include <vector>
10 
11 #include "base/logging.h"
12 #include "base/stl_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "build/build_config.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "ui/gfx/font_list.h"
17 #include "ui/gfx/geometry/insets.h"
18 #include "ui/gfx/geometry/point.h"
19 #include "ui/gfx/geometry/rect.h"
20 
21 namespace gfx {
22 namespace {
23 
24 const base::char16 kAcceleratorChar = '&';
25 
26 struct RemoveAcceleratorCharData {
27   const char* input;
28   int accelerated_char_pos;
29   int accelerated_char_span;
30   const char* output;
31   const char* name;
32 };
33 
TEST(TextUtilsTest,GetStringWidth)34 TEST(TextUtilsTest, GetStringWidth) {
35   FontList font_list;
36   EXPECT_EQ(GetStringWidth(base::string16(), font_list), 0);
37   EXPECT_GT(GetStringWidth(base::ASCIIToUTF16("a"), font_list),
38             GetStringWidth(base::string16(), font_list));
39   EXPECT_GT(GetStringWidth(base::ASCIIToUTF16("ab"), font_list),
40             GetStringWidth(base::ASCIIToUTF16("a"), font_list));
41   EXPECT_GT(GetStringWidth(base::ASCIIToUTF16("abc"), font_list),
42             GetStringWidth(base::ASCIIToUTF16("ab"), font_list));
43 }
44 
TEST(TextUtilsTest,GetStringSize)45 TEST(TextUtilsTest, GetStringSize) {
46   std::vector<base::string16> strings{
47       base::string16(),
48       base::ASCIIToUTF16("a"),
49       base::ASCIIToUTF16("abc"),
50   };
51 
52   FontList font_list;
53   for (base::string16 string : strings) {
54     gfx::Size size = GetStringSize(string, font_list);
55     EXPECT_EQ(GetStringWidth(string, font_list), size.width())
56         << " input string is \"" << string << "\"";
57     EXPECT_EQ(font_list.GetHeight(), size.height())
58         << " input string is \"" << string << "\"";
59   }
60 }
61 
TEST(TextUtilsTest,AdjustVisualBorderForFont_BorderLargerThanFont)62 TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderLargerThanFont) {
63   FontList font_list;
64 
65   // We will make some assumptions about the default font - specifically that it
66   // has leading space and space for the descender.
67   DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight());
68   DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight());
69 
70   // Adjust a large border for the default font. Using a large number means that
71   // the border will extend outside the leading and descender area of the font.
72   constexpr gfx::Insets kOriginalBorder(20);
73   const gfx::Insets result =
74       AdjustVisualBorderForFont(font_list, kOriginalBorder);
75   EXPECT_EQ(result.left(), kOriginalBorder.left());
76   EXPECT_EQ(result.right(), kOriginalBorder.right());
77   EXPECT_LT(result.top(), kOriginalBorder.top());
78   EXPECT_LT(result.bottom(), kOriginalBorder.bottom());
79 }
80 
TEST(TextUtilsTest,AdjustVisualBorderForFont_BorderSmallerThanFont)81 TEST(TextUtilsTest, AdjustVisualBorderForFont_BorderSmallerThanFont) {
82   FontList font_list;
83 
84   // We will make some assumptions about the default font - specifically that it
85   // has leading space and space for the descender.
86   DCHECK_GT(font_list.GetBaseline(), font_list.GetCapHeight());
87   DCHECK_LT(font_list.GetBaseline(), font_list.GetHeight());
88 
89   // Adjust a border with a small vertical component. The vertical component
90   // should go to zero because it overlaps the leading and descender areas of
91   // the font.
92   constexpr gfx::Insets kSmallVerticalInsets(1, 20);
93   const gfx::Insets result =
94       AdjustVisualBorderForFont(font_list, kSmallVerticalInsets);
95   EXPECT_EQ(result.left(), kSmallVerticalInsets.left());
96   EXPECT_EQ(result.right(), kSmallVerticalInsets.right());
97   EXPECT_EQ(result.top(), 0);
98   EXPECT_EQ(result.bottom(), 0);
99 }
100 
TEST(TextUtilsTest,GetFontCapHeightCenterOffset_SecondFontIsSmaller)101 TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsSmaller) {
102   FontList original_font;
103   FontList smaller_font = original_font.DeriveWithSizeDelta(-3);
104   DCHECK_LT(smaller_font.GetCapHeight(), original_font.GetCapHeight());
105   EXPECT_GT(GetFontCapHeightCenterOffset(original_font, smaller_font), 0);
106 }
107 
TEST(TextUtilsTest,GetFontCapHeightCenterOffset_SecondFontIsLarger)108 TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SecondFontIsLarger) {
109   FontList original_font;
110   FontList larger_font = original_font.DeriveWithSizeDelta(3);
111   DCHECK_GT(larger_font.GetCapHeight(), original_font.GetCapHeight());
112   EXPECT_LT(GetFontCapHeightCenterOffset(original_font, larger_font), 0);
113 }
114 
TEST(TextUtilsTest,GetFontCapHeightCenterOffset_SameSize)115 TEST(TextUtilsTest, GetFontCapHeightCenterOffset_SameSize) {
116   FontList original_font;
117   EXPECT_EQ(0, GetFontCapHeightCenterOffset(original_font, original_font));
118 }
119 
120 class RemoveAcceleratorCharTest
121     : public testing::TestWithParam<RemoveAcceleratorCharData> {
122  public:
123   static const RemoveAcceleratorCharData kCases[];
124 };
125 
126 const RemoveAcceleratorCharData RemoveAcceleratorCharTest::kCases[] = {
127     {"", -1, 0, "", "EmptyString"},
128     {"&", -1, 0, "", "AcceleratorCharOnly"},
129     {"no accelerator", -1, 0, "no accelerator", "NoAccelerator"},
130     {"&one accelerator", 0, 1, "one accelerator", "OneAccelerator_Start"},
131     {"one &accelerator", 4, 1, "one accelerator", "OneAccelerator_Middle"},
132     {"one_accelerator&", -1, 0, "one_accelerator", "OneAccelerator_End"},
133     {"&two &accelerators", 4, 1, "two accelerators",
134      "TwoAccelerators_OneAtStart"},
135     {"two &accelerators&", 4, 1, "two accelerators",
136      "TwoAccelerators_OneAtEnd"},
137     {"two& &accelerators", 4, 1, "two accelerators",
138      "TwoAccelerators_SpaceBetween"},
139     {"&&escaping", -1, 0, "&escaping", "Escape_Start"},
140     {"escap&&ing", -1, 0, "escap&ing", "Escape_Middle"},
141     {"escaping&&", -1, 0, "escaping&", "Escape_End"},
142     {"&mix&&ed", 0, 1, "mix&ed", "Mixed_EscapeAfterAccelerator"},
143     {"&&m&ix&&e&d&", 6, 1, "&mix&ed", "Mixed_MiddleAcceleratorSkipped"},
144     {"&&m&&ix&ed&&", 5, 1, "&m&ixed&", "Mixed_OneAccelerator"},
145     {"&m&&ix&ed&&", 4, 1, "m&ixed&", "Mixed_InitialAcceleratorSkipped"},
146     // U+1D49C MATHEMATICAL SCRIPT CAPITAL A, which occupies two |char16|'s.
147     {"&\U0001D49C", 0, 2, "\U0001D49C", "MultibyteAccelerator_Start"},
148     {"Test&\U0001D49Cing", 4, 2, "Test\U0001D49Cing",
149      "MultibyteAccelerator_Middle"},
150     {"Test\U0001D49C&ing", 6, 1, "Test\U0001D49Cing",
151      "OneAccelerator_AfterMultibyte"},
152     {"Test&\U0001D49C&ing", 6, 1, "Test\U0001D49Cing",
153      "MultibyteAccelerator_Skipped"},
154     {"Test&\U0001D49C&&ing", 4, 2, "Test\U0001D49C&ing",
155      "MultibyteAccelerator_EscapeAfter"},
156     {"Test&\U0001D49C&\U0001D49Cing", 6, 2, "Test\U0001D49C\U0001D49Cing",
157      "MultibyteAccelerator_AfterMultibyteAccelerator"},
158 };
159 
160 INSTANTIATE_TEST_SUITE_P(
161     All,
162     RemoveAcceleratorCharTest,
163     testing::ValuesIn(RemoveAcceleratorCharTest::kCases),
__anonaa60d28e0202(const testing::TestParamInfo<RemoveAcceleratorCharData>& param_info) 164     [](const testing::TestParamInfo<RemoveAcceleratorCharData>& param_info) {
165       return param_info.param.name;
166     });
167 
TEST_P(RemoveAcceleratorCharTest,RemoveAcceleratorChar)168 TEST_P(RemoveAcceleratorCharTest, RemoveAcceleratorChar) {
169   RemoveAcceleratorCharData data = GetParam();
170   int accelerated_char_pos;
171   int accelerated_char_span;
172   base::string16 result =
173       RemoveAcceleratorChar(base::UTF8ToUTF16(data.input), kAcceleratorChar,
174                             &accelerated_char_pos, &accelerated_char_span);
175   EXPECT_EQ(result, base::UTF8ToUTF16(data.output));
176   EXPECT_EQ(accelerated_char_pos, data.accelerated_char_pos);
177   EXPECT_EQ(accelerated_char_span, data.accelerated_char_span);
178 }
179 
180 struct FindValidBoundaryData {
181   const char16_t* input;
182   size_t index_in;
183   bool trim_whitespace;
184   size_t index_out;
185   const char* name;
186 };
187 
188 class FindValidBoundaryBeforeTest
189     : public testing::TestWithParam<FindValidBoundaryData> {
190  public:
191   static const FindValidBoundaryData kCases[];
192 };
193 
194 const FindValidBoundaryData FindValidBoundaryBeforeTest::kCases[] = {
195     {u"", 0, false, 0, "Empty"},
196     {u"word", 0, false, 0, "StartOfString"},
197     {u"word", 4, false, 4, "EndOfString"},
198     {u"word", 2, false, 2, "MiddleOfString_OnValidCharacter"},
199     {u"w��d", 2, false, 1, "MiddleOfString_OnSurrogatePair"},
200     {u"w��d", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"},
201     {u"w��d", 3, false, 3, "MiddleOfString_AfterSurrogatePair"},
202     {u"wo d", 3, false, 3, "MiddleOfString_OnSpace_NoTrim"},
203     {u"wo d", 3, true, 2, "MiddleOfString_OnSpace_Trim"},
204     {u"wo d", 2, false, 2, "MiddleOfString_LeftOfSpace_NoTrim"},
205     {u"wo d", 2, true, 2, "MiddleOfString_LeftOfSpace_Trim"},
206     {u"wo\td", 3, false, 3, "MiddleOfString_OnTab_NoTrim"},
207     {u"wo\td", 3, true, 2, "MiddleOfString_OnTab_Trim"},
208     {u"w  d", 3, false, 3, "MiddleOfString_MultipleWhitespace_NoTrim"},
209     {u"w  d", 3, true, 1, "MiddleOfString_MultipleWhitespace_Trim"},
210     {u"w  d", 2, false, 2, "MiddleOfString_MiddleOfWhitespace_NoTrim"},
211     {u"w  d", 2, true, 1, "MiddleOfString_MiddleOfWhitespace_Trim"},
212     {u"w  ", 3, false, 3, "EndOfString_Whitespace_NoTrim"},
213     {u"w  ", 3, true, 1, "EndOfString_Whitespace_Trim"},
214     {u"  d", 2, false, 2, "MiddleOfString_Whitespace_NoTrim"},
215     {u"  d", 2, true, 0, "MiddleOfString_Whitespace_Trim"},
216     // COMBINING GRAVE ACCENT (U+0300)
217     {u"wo\u0300d", 2, false, 1, "MiddleOfString_OnCombiningMark"},
218     {u"wo\u0300d", 1, false, 1, "MiddleOfString_BeforeCombiningMark"},
219     {u"wo\u0300d", 3, false, 3, "MiddleOfString_AfterCombiningMark"},
220     {u"w o\u0300d", 3, true, 1, "MiddleOfString_SpaceAndCombinginMark_Trim"},
221     {u"wo\u0300 d", 3, true, 3, "MiddleOfString_CombiningMarkAndSpace_Trim"},
222     {u"w \u0300d", 3, true, 3,
223      "MiddleOfString_AfterSpaceWithCombiningMark_Trim"},
224     {u"w \u0300d", 2, true, 1, "MiddleOfString_OnSpaceWithCombiningMark_Trim"},
225     {u"w \u0300 d", 4, true, 3,
226      "MiddleOfString_AfterSpaceAfterSpaceWithCombiningMark_Trim"},
227     // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1
228     // (U+1D16E)
229     {u"w\U0001D11E\U0001D16Ed", 1, false, 1,
230      "MiddleOfString_BeforeCombiningSurrogate"},
231     {u"w\U0001D11E\U0001D16Ed", 2, false, 1,
232      "MiddleOfString_OnCombiningSurrogate_Pos1"},
233     {u"w\U0001D11E\U0001D16Ed", 3, false, 1,
234      "MiddleOfString_OnCombiningSurrogate_Pos2"},
235     {u"w\U0001D11E\U0001D16Ed", 4, false, 1,
236      "MiddleOfString_OnCombiningSurrogate_Pos3"},
237     {u"w\U0001D11E\U0001D16Ed", 5, false, 5,
238      "MiddleOfString_AfterCombiningSurrogate"},
239 };
240 
241 INSTANTIATE_TEST_SUITE_P(
242     All,
243     FindValidBoundaryBeforeTest,
244     testing::ValuesIn(FindValidBoundaryBeforeTest::kCases),
__anonaa60d28e0302(const testing::TestParamInfo<FindValidBoundaryData>& param_info) 245     [](const testing::TestParamInfo<FindValidBoundaryData>& param_info) {
246       return param_info.param.name;
247     });
248 
TEST_P(FindValidBoundaryBeforeTest,FindValidBoundaryBefore)249 TEST_P(FindValidBoundaryBeforeTest, FindValidBoundaryBefore) {
250   FindValidBoundaryData data = GetParam();
251   const base::string16::const_pointer input =
252       reinterpret_cast<base::string16::const_pointer>(data.input);
253   DLOG(INFO) << input;
254   size_t result =
255       FindValidBoundaryBefore(input, data.index_in, data.trim_whitespace);
256   EXPECT_EQ(data.index_out, result);
257 }
258 
259 class FindValidBoundaryAfterTest
260     : public testing::TestWithParam<FindValidBoundaryData> {
261  public:
262   static const FindValidBoundaryData kCases[];
263 };
264 
265 const FindValidBoundaryData FindValidBoundaryAfterTest::kCases[] = {
266     {u"", 0, false, 0, "Empty"},
267     {u"word", 0, false, 0, "StartOfString"},
268     {u"word", 4, false, 4, "EndOfString"},
269     {u"word", 2, false, 2, "MiddleOfString_OnValidCharacter"},
270     {u"w��d", 2, false, 3, "MiddleOfString_OnSurrogatePair"},
271     {u"w��d", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"},
272     {u"w��d", 3, false, 3, "MiddleOfString_AfterSurrogatePair"},
273     {u"wo d", 2, false, 2, "MiddleOfString_OnSpace_NoTrim"},
274     {u"wo d", 2, true, 3, "MiddleOfString_OnSpace_Trim"},
275     {u"wo d", 3, false, 3, "MiddleOfString_RightOfSpace_NoTrim"},
276     {u"wo d", 3, true, 3, "MiddleOfString_RightOfSpace_Trim"},
277     {u"wo\td", 2, false, 2, "MiddleOfString_OnTab_NoTrim"},
278     {u"wo\td", 2, true, 3, "MiddleOfString_OnTab_Trim"},
279     {u"w  d", 1, false, 1, "MiddleOfString_MultipleWhitespace_NoTrim"},
280     {u"w  d", 1, true, 3, "MiddleOfString_MultipleWhitespace_Trim"},
281     {u"w  d", 2, false, 2, "MiddleOfString_MiddleOfWhitespace_NoTrim"},
282     {u"w  d", 2, true, 3, "MiddleOfString_MiddleOfWhitespace_Trim"},
283     {u"w  ", 1, false, 1, "MiddleOfString_Whitespace_NoTrim"},
284     {u"w  ", 1, true, 3, "MiddleOfString_Whitespace_Trim"},
285     {u"  d", 0, false, 0, "StartOfString_Whitespace_NoTrim"},
286     {u"  d", 0, true, 2, "StartOfString_Whitespace_Trim"},
287     // COMBINING GRAVE ACCENT (U+0300)
288     {u"wo\u0300d", 2, false, 3, "MiddleOfString_OnCombiningMark"},
289     {u"wo\u0300d", 1, false, 1, "MiddleOfString_BeforeCombiningMark"},
290     {u"wo\u0300d", 3, false, 3, "MiddleOfString_AfterCombiningMark"},
291     {u"w o\u0300d", 1, true, 2, "MiddleOfString_SpaceAndCombinginMark_Trim"},
292     {u"wo\u0300 d", 1, true, 1,
293      "MiddleOfString_BeforeCombiningMarkAndSpace_Trim"},
294     {u"wo\u0300 d", 2, true, 4, "MiddleOfString_OnCombiningMarkAndSpace_Trim"},
295     {u"w \u0300d", 1, true, 1,
296      "MiddleOfString_BeforeSpaceWithCombiningMark_Trim"},
297     {u"w \u0300d", 2, true, 3, "MiddleOfString_OnSpaceWithCombiningMark_Trim"},
298     {u"w  \u0300d", 1, true, 2,
299      "MiddleOfString_BeforeSpaceBeforeSpaceWithCombiningMark_Trim"},
300     // MUSICAL SYMBOL G CLEF (U+1D11E) + MUSICAL SYMBOL COMBINING FLAG-1
301     // (U+1D16E)
302     {u"w\U0001D11E\U0001D16Ed", 1, false, 1,
303      "MiddleOfString_BeforeCombiningSurrogate"},
304     {u"w\U0001D11E\U0001D16Ed", 2, false, 5,
305      "MiddleOfString_OnCombiningSurrogate_Pos1"},
306     {u"w\U0001D11E\U0001D16Ed", 3, false, 5,
307      "MiddleOfString_OnCombiningSurrogate_Pos2"},
308     {u"w\U0001D11E\U0001D16Ed", 4, false, 5,
309      "MiddleOfString_OnCombiningSurrogate_Pos3"},
310     {u"w\U0001D11E\U0001D16Ed", 5, false, 5,
311      "MiddleOfString_AfterCombiningSurrogate"},
312 };
313 
314 INSTANTIATE_TEST_SUITE_P(
315     All,
316     FindValidBoundaryAfterTest,
317     testing::ValuesIn(FindValidBoundaryAfterTest::kCases),
__anonaa60d28e0402(const testing::TestParamInfo<FindValidBoundaryData>& param_info) 318     [](const testing::TestParamInfo<FindValidBoundaryData>& param_info) {
319       return param_info.param.name;
320     });
321 
TEST_P(FindValidBoundaryAfterTest,FindValidBoundaryAfter)322 TEST_P(FindValidBoundaryAfterTest, FindValidBoundaryAfter) {
323   FindValidBoundaryData data = GetParam();
324   const base::string16::const_pointer input =
325       reinterpret_cast<base::string16::const_pointer>(data.input);
326   size_t result =
327       FindValidBoundaryAfter(input, data.index_in, data.trim_whitespace);
328   EXPECT_EQ(data.index_out, result);
329 }
330 
331 }  // namespace
332 }  // namespace gfx
333