blob: f1824dcb2a8f161b15bfa83da814a1e04c17a9f7 [file] [log] [blame]
// 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 <stddef.h>
#include <vector>
#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<std::u16string> 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<RemoveAcceleratorCharData> {
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<RemoveAcceleratorCharData>& 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<FindValidBoundaryData> {
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<FindValidBoundaryData>& param_info) {
return param_info.param.name;
});
TEST_P(FindValidBoundaryBeforeTest, FindValidBoundaryBefore) {
FindValidBoundaryData data = GetParam();
const std::u16string::const_pointer input =
reinterpret_cast<std::u16string::const_pointer>(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<FindValidBoundaryData> {
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<FindValidBoundaryData>& param_info) {
return param_info.param.name;
});
TEST_P(FindValidBoundaryAfterTest, FindValidBoundaryAfter) {
FindValidBoundaryData data = GetParam();
const std::u16string::const_pointer input =
reinterpret_cast<std::u16string::const_pointer>(data.input);
size_t result =
FindValidBoundaryAfter(input, data.index_in, data.trim_whitespace);
EXPECT_EQ(data.index_out, result);
}
} // namespace
} // namespace gfx