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"wd", 2, false, 1, "MiddleOfString_OnSurrogatePair"},
200 {u"wd", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"},
201 {u"wd", 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"wd", 2, false, 3, "MiddleOfString_OnSurrogatePair"},
271 {u"wd", 1, false, 1, "MiddleOfString_BeforeSurrogatePair"},
272 {u"wd", 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