mirror of
https://github.com/fmtlib/fmt.git
synced 2026-10-30 19:07:51 +08:00
Fix Unicode width, precision and escaping for wide strings
This commit is contained in:
parent
4afd0e498e
commit
03c047a2ec
@ -665,6 +665,25 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) {
|
||||
} while (buf_ptr < buf + num_chars_left);
|
||||
}
|
||||
|
||||
template <typename Char, typename F,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
FMT_CONSTEXPR void for_each_codepoint(basic_string_view<Char> s, F f) {
|
||||
auto p = s.begin(), end = s.end();
|
||||
while (p != end) {
|
||||
auto begin = p;
|
||||
uint32_t cp = static_cast<unsigned_char<Char>>(*p++);
|
||||
// Leave unpaired surrogates intact so they can be escaped individually.
|
||||
if (sizeof(Char) == 2 && cp >= 0xd800 && cp <= 0xdbff && p != end) {
|
||||
uint32_t low = static_cast<unsigned_char<Char>>(*p);
|
||||
if (low >= 0xdc00 && low <= 0xdfff) {
|
||||
cp = 0x10000 + ((cp - 0xd800) << 10) + (low - 0xdc00);
|
||||
++p;
|
||||
}
|
||||
}
|
||||
if (!f(cp, basic_string_view<Char>(begin, to_unsigned(p - begin)))) return;
|
||||
}
|
||||
}
|
||||
|
||||
struct wide_cp_range {
|
||||
uint32_t first;
|
||||
uint32_t last;
|
||||
@ -1982,12 +2001,17 @@ template <typename Char> struct find_escape_result {
|
||||
template <typename Char>
|
||||
auto find_escape(const Char* begin, const Char* end)
|
||||
-> find_escape_result<Char> {
|
||||
for (; begin != end; ++begin) {
|
||||
uint32_t cp = static_cast<unsigned_char<Char>>(*begin);
|
||||
if (sizeof(Char) == 1 && cp >= 0x80) continue;
|
||||
if (needs_escape(cp)) return {begin, begin + 1, cp};
|
||||
}
|
||||
return {begin, nullptr, 0};
|
||||
auto result = find_escape_result<Char>{end, nullptr, 0};
|
||||
for_each_codepoint(basic_string_view<Char>(begin, to_unsigned(end - begin)),
|
||||
[&](uint32_t cp, basic_string_view<Char> sv) {
|
||||
if (sizeof(Char) == 1 && cp >= 0x80) return true;
|
||||
if (needs_escape(cp)) {
|
||||
result = {sv.begin(), sv.end(), cp};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
inline auto find_escape(const char* begin, const char* end)
|
||||
@ -2406,14 +2430,13 @@ FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs,
|
||||
: write<Char>(out, static_cast<unsigned_type>(value), specs, loc);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(std::is_same<Char, char>::value)>
|
||||
template <typename Char, typename OutputIt>
|
||||
FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
|
||||
const format_specs& specs) -> OutputIt {
|
||||
bool is_debug = specs.type() == presentation_type::debug;
|
||||
if (specs.precision < 0 && specs.width == 0) {
|
||||
auto&& it = reserve(out, s.size());
|
||||
return is_debug ? write_escaped_string(it, s) : copy<char>(s, it);
|
||||
return is_debug ? write_escaped_string(it, s) : copy<Char>(s, it);
|
||||
}
|
||||
|
||||
size_t display_width_limit =
|
||||
@ -2421,12 +2444,13 @@ FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
|
||||
size_t display_width =
|
||||
!is_debug || specs.precision == 0 ? 0 : 1; // Account for opening '"'.
|
||||
size_t size = !is_debug || specs.precision == 0 ? 0 : 1;
|
||||
for_each_codepoint(s, [&](uint32_t cp, string_view sv) {
|
||||
if (is_debug && needs_escape(cp)) {
|
||||
counting_buffer<char> buf;
|
||||
write_escaped_cp(basic_appender<char>(buf),
|
||||
find_escape_result<char>{sv.begin(), sv.end(), cp});
|
||||
// We're reinterpreting bytes as display width. That's okay
|
||||
for_each_codepoint(s, [&](uint32_t cp, basic_string_view<Char> sv) {
|
||||
if (is_debug && needs_escape(cp) &&
|
||||
(sizeof(Char) != 1 || std::is_same<Char, char>::value || cp < 0x80)) {
|
||||
counting_buffer<Char> buf;
|
||||
write_escaped_cp(basic_appender<Char>(buf),
|
||||
find_escape_result<Char>{sv.begin(), sv.end(), cp});
|
||||
// We're reinterpreting code units as display width. That's okay
|
||||
// because write_escaped_cp() only writes ASCII characters.
|
||||
size_t cp_width = buf.count();
|
||||
if (display_width + cp_width <= display_width_limit) {
|
||||
@ -2478,7 +2502,7 @@ FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
|
||||
FMT_CONSTEXPR auto operator++(int) -> bounded_output_iterator& {
|
||||
return *this;
|
||||
}
|
||||
FMT_CONSTEXPR auto operator=(char c) -> bounded_output_iterator& {
|
||||
FMT_CONSTEXPR auto operator=(Char c) -> bounded_output_iterator& {
|
||||
if (bound > 0) {
|
||||
*underlying_iterator++ = c;
|
||||
--bound;
|
||||
@ -2487,35 +2511,12 @@ FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
|
||||
}
|
||||
};
|
||||
|
||||
return write_padded<char>(
|
||||
return write_padded<Char>(
|
||||
out, specs, size, display_width, [=](reserve_iterator<OutputIt> it) {
|
||||
return is_debug
|
||||
? write_escaped_string(bounded_output_iterator{it, size}, s)
|
||||
.underlying_iterator
|
||||
: copy<char>(s.data(), s.data() + size, it);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
|
||||
const format_specs& specs) -> OutputIt {
|
||||
auto data = s.data();
|
||||
auto size = s.size();
|
||||
if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
|
||||
size = to_unsigned(specs.precision);
|
||||
|
||||
bool is_debug = specs.type() == presentation_type::debug;
|
||||
if (is_debug) {
|
||||
auto buf = counting_buffer<Char>();
|
||||
write_escaped_string(basic_appender<Char>(buf), s);
|
||||
size = buf.count();
|
||||
}
|
||||
|
||||
return write_padded<Char>(
|
||||
out, specs, size, [=](reserve_iterator<OutputIt> it) {
|
||||
return is_debug ? write_escaped_string(it, s)
|
||||
: copy<Char>(data, data + size, it);
|
||||
: copy<Char>(s.data(), s.data() + size, it);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -298,6 +298,98 @@ TEST(xchar_test, escape_string) {
|
||||
EXPECT_EQ(fmt::format(L"{}", vec{L"понедельник"}), L"[\"понедельник\"]");
|
||||
}
|
||||
|
||||
TEST(xchar_test, utf16_string_width) {
|
||||
EXPECT_EQ(fmt::format(u"{:>4}", u"\u4e2d"), u" \u4e2d");
|
||||
EXPECT_EQ(fmt::format(u"{:^5}", u"\U0001f600"), u" \U0001f600 ");
|
||||
EXPECT_EQ(fmt::format(u"{:>{}}", u"\U00010000", 3), u" \U00010000");
|
||||
EXPECT_EQ(fmt::format(u"{:<5}", u"a\U0001f600"), u"a\U0001f600 ");
|
||||
}
|
||||
|
||||
TEST(xchar_test, utf16_string_precision) {
|
||||
EXPECT_EQ(fmt::format(u"{:.1}", u"\U00010000x"), u"\U00010000");
|
||||
EXPECT_EQ(fmt::format(u"{:.1}", u"\U0001f600x"), u"");
|
||||
EXPECT_EQ(fmt::format(u"{:.2}", u"\U0001f600x"), u"\U0001f600");
|
||||
EXPECT_EQ(fmt::format(u"{:.3}", u"a\U0001f600x"), u"a\U0001f600");
|
||||
EXPECT_EQ(fmt::format(u"{:>4.2}", u"\U0001f600x"), u" \U0001f600");
|
||||
EXPECT_EQ(fmt::format(u"{:.0}", u"\U0001f600"), u"");
|
||||
const char16_t input[] = {u'a', 0, 0xd800, 0xdc00};
|
||||
auto view = fmt::basic_string_view<char16_t>(input, 4);
|
||||
EXPECT_EQ(fmt::format(u"{:.3}", view), std::u16string(input, 4));
|
||||
}
|
||||
|
||||
TEST(xchar_test, utf16_debug_string) {
|
||||
EXPECT_EQ(fmt::format(u"{:?}", u"\U0001f600"), u"\"\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(u"{:?}", u"a\U00010000\n\U0001f600"),
|
||||
u"\"a\U00010000\\n\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(u"{:?}", u"\U0001d173"), u"\"\\U0001d173\"");
|
||||
EXPECT_EQ(fmt::format(u"{:?}", u"\U0010ffff"), u"\"\\U0010ffff\"");
|
||||
auto values = std::vector<std::u16string>{u"\U0001f600", u"\U0001d173"};
|
||||
EXPECT_EQ(fmt::format(u"{}", values), u"[\"\U0001f600\", \"\\U0001d173\"]");
|
||||
}
|
||||
|
||||
TEST(xchar_test, utf16_debug_width_and_precision) {
|
||||
EXPECT_EQ(fmt::format(u"{:>6?}", u"\U0001f600"), u" \"\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(u"{:.0?}", u"\U0001f600"), u"");
|
||||
EXPECT_EQ(fmt::format(u"{:.1?}", u"\U0001f600"), u"\"");
|
||||
EXPECT_EQ(fmt::format(u"{:.2?}", u"\U0001f600"), u"\"");
|
||||
EXPECT_EQ(fmt::format(u"{:.3?}", u"\U0001f600"), u"\"\U0001f600");
|
||||
EXPECT_EQ(fmt::format(u"{:.4?}", u"\U0001f600"), u"\"\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(u"{:>6.3?}", u"\U0001f600"), u" \"\U0001f600");
|
||||
EXPECT_EQ(fmt::format(u"{:.4?}", u"\U0001d173"), u"\"\\U0");
|
||||
EXPECT_EQ(fmt::format(u"{:.1?}", u""), u"\"");
|
||||
EXPECT_EQ(fmt::format(u"{:.2?}", u""), u"\"\"");
|
||||
}
|
||||
|
||||
TEST(xchar_test, utf16_debug_invalid_sequences) {
|
||||
const char16_t high[] = {0xd800, 0};
|
||||
const char16_t low[] = {0xdc00, 0};
|
||||
const char16_t high_ascii[] = {0xd800, u'x', 0};
|
||||
const char16_t high_pair[] = {0xd800, 0xd800, 0xdc00, 0};
|
||||
EXPECT_EQ(fmt::format(u"{:?}", high), u"\"\\ud800\"");
|
||||
EXPECT_EQ(fmt::format(u"{:?}", low), u"\"\\udc00\"");
|
||||
EXPECT_EQ(fmt::format(u"{:?}", high_ascii), u"\"\\ud800x\"");
|
||||
EXPECT_EQ(fmt::format(u"{:?}", high_pair), u"\"\\ud800\U00010000\"");
|
||||
EXPECT_EQ(fmt::format(u"{:.1}", high_pair), std::u16string(high_pair, 1));
|
||||
}
|
||||
|
||||
TEST(xchar_test, utf32_string_width_and_precision) {
|
||||
EXPECT_EQ(fmt::format(U"{:>4}", U"\u4e2d"), U" \u4e2d");
|
||||
EXPECT_EQ(fmt::format(U"{:^5}", U"\U0001f600"), U" \U0001f600 ");
|
||||
EXPECT_EQ(fmt::format(U"{:.1}", U"\U0001f600x"), U"");
|
||||
EXPECT_EQ(fmt::format(U"{:.2}", U"\U0001f600x"), U"\U0001f600");
|
||||
EXPECT_EQ(fmt::format(U"{:>6?}", U"\U0001f600"), U" \"\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(U"{:.4?}", U"\U0001d173"), U"\"\\U0");
|
||||
}
|
||||
|
||||
TEST(xchar_test, wide_string_width_and_precision) {
|
||||
EXPECT_EQ(fmt::format(L"{:>4}", L"\u4e2d"), L" \u4e2d");
|
||||
EXPECT_EQ(fmt::format(L"{:.1}", L"\U0001f600x"), L"");
|
||||
EXPECT_EQ(fmt::format(L"{:.2}", L"\U0001f600x"), L"\U0001f600");
|
||||
EXPECT_EQ(fmt::format(L"{:?}", L"\U0001f600"), L"\"\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(L"{:>6?}", L"\U0001f600"), L" \"\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(L"{:.4?}", L"\U0001d173"), L"\"\\U0");
|
||||
}
|
||||
|
||||
TEST(xchar_test, utf16_format_to) {
|
||||
auto output = std::u16string();
|
||||
fmt::format_to(std::back_inserter(output), u"{:>{}.{}}", u"\U0001f600x", 4,
|
||||
2);
|
||||
EXPECT_EQ(output, u" \U0001f600");
|
||||
char16_t buffer[4] = {};
|
||||
auto result = fmt::format_to_n(buffer, 4, u"{:>6?}", u"\U0001f600");
|
||||
EXPECT_EQ(std::u16string(buffer, 4), u" \"\xd83d");
|
||||
EXPECT_EQ(result.size, 6);
|
||||
EXPECT_EQ(result.out, buffer + 4);
|
||||
}
|
||||
|
||||
#ifdef __cpp_char8_t
|
||||
TEST(xchar_test, char8_debug_string_width) {
|
||||
EXPECT_EQ(fmt::format(u8"{:>7?}", u8"\u4e2d"), u8" \"\u4e2d\"");
|
||||
EXPECT_EQ(fmt::format(u8"{:>8?}", u8"\U0001f600"), u8" \"\U0001f600\"");
|
||||
EXPECT_EQ(fmt::format(u8"{:>6?}", u8"\n"), u8" \"\\n\"");
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(xchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user