mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2025-09-19 18:18:09 +08:00
Bugfix Input use std::string (#279)
Use std::string by default for the implementation of FTXUI's input component. Along the way: - Give a correct implementation for fullwidth characters. - Add tests - Modify the way the cursor is drawn.
This commit is contained in:
@@ -288,6 +288,98 @@ std::vector<std::string> Utf8ToGlyphs(const std::string& input) {
|
||||
return out;
|
||||
}
|
||||
|
||||
int GlyphPosition(const std::string& input, size_t glyph_to_skip, size_t start) {
|
||||
if (glyph_to_skip <= 0)
|
||||
return 0;
|
||||
size_t end = 0;
|
||||
while (start < input.size()) {
|
||||
uint32_t codepoint;
|
||||
bool eaten = EatCodePoint(input, start, &end, &codepoint);
|
||||
|
||||
// Ignore invalid, control characters and combining characters.
|
||||
if (!eaten || IsControl(codepoint) || IsCombining(codepoint)) {
|
||||
start = end;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We eat the beginning of the next glyph. If we are eating the one
|
||||
// requested, return its start position immediately.
|
||||
if (glyph_to_skip == 0)
|
||||
return start;
|
||||
|
||||
// Otherwise, skip this glyph and iterate:
|
||||
glyph_to_skip--;
|
||||
start = end;
|
||||
}
|
||||
return input.size();
|
||||
}
|
||||
|
||||
std::vector<int> CellToGlyphIndex(const std::string& input) {
|
||||
int x = -1;
|
||||
std::vector<int> out;
|
||||
out.reserve(input.size());
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
while (start < input.size()) {
|
||||
uint32_t codepoint;
|
||||
bool eaten = EatCodePoint(input, start, &end, &codepoint);
|
||||
start = end;
|
||||
|
||||
// Ignore invalid / control characters.
|
||||
if (!eaten || IsControl(codepoint))
|
||||
continue;
|
||||
|
||||
// Combining characters are put with the previous glyph they are modifying.
|
||||
if (IsCombining(codepoint)) {
|
||||
if (x == -1) {
|
||||
++x;
|
||||
out.push_back(x);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fullwidth characters take two cells. The second is made of the empty
|
||||
// string to reserve the space the first is taking.
|
||||
if (IsFullWidth(codepoint)) {
|
||||
++x;
|
||||
out.push_back(x);
|
||||
out.push_back(x);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normal characters:
|
||||
++x;
|
||||
out.push_back(x);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int GlyphCount(const std::string& input) {
|
||||
int size = 0;
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
while (start < input.size()) {
|
||||
uint32_t codepoint;
|
||||
bool eaten = EatCodePoint(input, start, &end, &codepoint);
|
||||
start = end;
|
||||
|
||||
// Ignore invalid characters:
|
||||
if (!eaten || IsControl(codepoint))
|
||||
continue;
|
||||
|
||||
// Ignore combining characters, except when they don't have a preceding to
|
||||
// combine with.
|
||||
if (IsCombining(codepoint)) {
|
||||
if (size == 0)
|
||||
size++;
|
||||
continue;
|
||||
}
|
||||
|
||||
size++;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996) // codecvt_utf8_utf16 is deprecated
|
||||
|
@@ -42,6 +42,85 @@ TEST(StringTest, Utf8ToGlyphs) {
|
||||
EXPECT_EQ(Utf8ToGlyphs("a\1a"), T({"a", "a"}));
|
||||
}
|
||||
|
||||
TEST(StringTest, GlyphCount) {
|
||||
// Basic:
|
||||
EXPECT_EQ(GlyphCount(""), 0);
|
||||
EXPECT_EQ(GlyphCount("a"), 1);
|
||||
EXPECT_EQ(GlyphCount("ab"), 2);
|
||||
// Fullwidth glyphs:
|
||||
EXPECT_EQ(GlyphCount("测"), 1);
|
||||
EXPECT_EQ(GlyphCount("测试"), 2);
|
||||
// Combining characters:
|
||||
EXPECT_EQ(GlyphCount("ā"), 1);
|
||||
EXPECT_EQ(GlyphCount("a⃒"), 1);
|
||||
EXPECT_EQ(GlyphCount("a̗"), 1);
|
||||
// Control characters:
|
||||
EXPECT_EQ(GlyphCount("\1"), 0);
|
||||
EXPECT_EQ(GlyphCount("a\1a"), 2);
|
||||
}
|
||||
|
||||
|
||||
TEST(StringTest, GlyphPosition) {
|
||||
// Basic:
|
||||
EXPECT_EQ(GlyphPosition("", -1), 0);
|
||||
EXPECT_EQ(GlyphPosition("", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("", 1), 0);
|
||||
EXPECT_EQ(GlyphPosition("a", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("a", 1), 1);
|
||||
EXPECT_EQ(GlyphPosition("ab", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("ab", 1), 1);
|
||||
EXPECT_EQ(GlyphPosition("ab", 2), 2);
|
||||
EXPECT_EQ(GlyphPosition("abc", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("abc", 1), 1);
|
||||
EXPECT_EQ(GlyphPosition("abc", 2), 2);
|
||||
EXPECT_EQ(GlyphPosition("abc", 3), 3);
|
||||
// Fullwidth glyphs:
|
||||
EXPECT_EQ(GlyphPosition("测", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("测", 1), 3);
|
||||
EXPECT_EQ(GlyphPosition("测试", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("测试", 1), 3);
|
||||
EXPECT_EQ(GlyphPosition("测试", 2), 6);
|
||||
EXPECT_EQ(GlyphPosition("测试", 1, 3), 6);
|
||||
EXPECT_EQ(GlyphPosition("测试", 1, 0), 3);
|
||||
// Combining characters:
|
||||
EXPECT_EQ(GlyphPosition("ā", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("ā", 1), 3);
|
||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 1), 4);
|
||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 2), 7);
|
||||
EXPECT_EQ(GlyphPosition("a⃒a̗ā", 3), 10);
|
||||
// Control characters:
|
||||
EXPECT_EQ(GlyphPosition("\1", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("\1", 1), 1);
|
||||
EXPECT_EQ(GlyphPosition("a\1a", 0), 0);
|
||||
EXPECT_EQ(GlyphPosition("a\1a", 1), 2);
|
||||
EXPECT_EQ(GlyphPosition("a\1a", 2), 3);
|
||||
}
|
||||
|
||||
TEST(StringTest, CellToGlyphIndex) {
|
||||
// Basic:
|
||||
auto basic = CellToGlyphIndex("abc");
|
||||
ASSERT_EQ(basic.size(), 3);
|
||||
EXPECT_EQ(basic[0], 0);
|
||||
EXPECT_EQ(basic[1], 1);
|
||||
EXPECT_EQ(basic[2], 2);
|
||||
|
||||
// Fullwidth glyphs:
|
||||
auto fullwidth = CellToGlyphIndex("测试");
|
||||
ASSERT_EQ(fullwidth.size(), 4);
|
||||
EXPECT_EQ(fullwidth[0], 0);
|
||||
EXPECT_EQ(fullwidth[1], 0);
|
||||
EXPECT_EQ(fullwidth[2], 1);
|
||||
EXPECT_EQ(fullwidth[3], 1);
|
||||
|
||||
// Combining characters:
|
||||
auto combining = CellToGlyphIndex("a⃒a̗ā");
|
||||
ASSERT_EQ(combining.size(), 3);
|
||||
EXPECT_EQ(combining[0], 0);
|
||||
EXPECT_EQ(combining[1], 1);
|
||||
EXPECT_EQ(combining[2], 2);
|
||||
}
|
||||
|
||||
// Copyright 2020 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
||||
|
Reference in New Issue
Block a user