27 [[nodiscard]]
constexpr bool is_surrogate(
char32_t cp)
noexcept;
30 [[nodiscard]]
constexpr bool is_unicode(
char32_t cp)
noexcept;
40 [[nodiscard]]
constexpr auto get_unicode_plane(
char32_t cp)
noexcept ->
unicode_plane;
45 unknown, utf8, utf16, utf32,
58 std::endian endianness;
60 [[nodiscard]]
constexpr auto operator<=>(
text_encoding const& other)
const noexcept =
default;
67 constexpr inline text_encoding utf8_encoding = { text_encoding_type::utf8, std::endian::native };
68 constexpr inline text_encoding utf16_le_encoding = { text_encoding_type::utf16, std::endian::little };
69 constexpr inline text_encoding utf16_be_encoding = { text_encoding_type::utf16, std::endian::big };
70 constexpr inline text_encoding utf32_le_encoding = { text_encoding_type::utf32, std::endian::little };
71 constexpr inline text_encoding utf32_be_encoding = { text_encoding_type::utf32, std::endian::big };
78 struct text_decode_result;
85 template <
bytelike BYTE_TYPE,
size_t N>
86 text_encoding
consume_bom(std::span<BYTE_TYPE, N>& spn);
102 template <
bytelike_range T>
113 constexpr char32_t consume_utf8(string_view8
auto& str);
121 constexpr char32_t peek_utf8(string_view8
auto str);
142 constexpr char32_t pop_utf8(string8
auto& str);
145 constexpr size_t append_utf8(string8
auto& buffer,
char32_t cp);
150 template <
string8 RESULT = std::
string>
151 [[nodiscard]]
constexpr RESULT
to_utf8(
char32_t cp);
156 template <
string8 RESULT = std::
string,
stringable16 STR>
157 [[nodiscard]]
constexpr RESULT
to_utf8(STR&& str);
162 template <
string8 RESULT = std::
string,
stringable32 STR>
163 [[nodiscard]]
constexpr RESULT
to_utf8(STR&& str);
166 [[nodiscard]] std::string
to_string(std::wstring_view str);
171 constexpr void transcode_codepage_to_utf8(string8
auto& dest, stringable8
auto source, std::span<char32_t const, 128> codepage_map);
178 template <
string8 RESULT = std::
string>
179 [[nodiscard]]
constexpr auto transcode_codepage_to_utf8(stringable8
auto source, std::span<char32_t const, 128> codepage_map) -> RESULT;
184 template <std::ranges::view R>
196 constexpr size_t append_utf16(string16
auto& buffer,
char32_t cp);
201 template <
string16 RESULT = std::w
string>
202 [[nodiscard]]
constexpr RESULT
to_utf16(
char32_t cp);
207 template <
string16 RESULT = std::w
string,
stringable8 STR>
208 [[nodiscard]]
constexpr RESULT
to_utf16(STR str);
212 [[nodiscard]] std::wstring
to_wstring(std::string_view str);
226 constexpr size_t append_utf32(string32
auto& buffer,
char32_t cp);
231 template <
typename T>
247 template <
typename T>
257 static_assert(
string8<T>,
"Unsupported character type");
261 template <
typename TO,
typename FROM>
264 auto from_sv = make_sv(from);
265 while (!std::empty(from_sv))
270 template <
typename TO,
typename FROM>
281 template <
typename T>
288 template <
typename RESULT = std::
string>
292 template <
typename FUNC>
293 void encode_flag(std::string_view ascii_country_code, FUNC&& encoding_func)
295 static constexpr char32_t flag_introducer_tag = U
'\U0001F3F4';
296 static constexpr char32_t flag_end_tag = U
'\U000E007F';
298 encoding_func(flag_introducer_tag);
299 for (
auto& cp : ascii_country_code)
301 const char32_t encoded = (uint8_t(cp) & 0x7F) + 0xE0000;
302 encoding_func(encoded);
304 encoding_func(flag_end_tag);
307 template <
typename RESULT = std::
string,
typename FUNC>
308 [[nodiscard]]
constexpr auto encode_flag(std::string_view ascii_country_code) -> RESULT
327 template <
bytelike BYTE_TYPE,
size_t N>
330 static_assert(N == std::dynamic_extent,
"consume_bom() can only be used with dynamic spans");
332 static constexpr auto bom_for_gb18030 = std::array{
static_cast<BYTE_TYPE
>(0x84),
static_cast<BYTE_TYPE
>(0x31),
static_cast<BYTE_TYPE
>(0x95),
static_cast<BYTE_TYPE
>(0x33) };
333 static constexpr auto bom_for_utf32_be = std::array{
static_cast<BYTE_TYPE
>(0x00),
static_cast<BYTE_TYPE
>(0x00),
static_cast<BYTE_TYPE
>(0xFE),
static_cast<BYTE_TYPE
>(0xFF) };
334 static constexpr auto bom_for_utf32_le = std::array{
static_cast<BYTE_TYPE
>(0xFF),
static_cast<BYTE_TYPE
>(0xFE),
static_cast<BYTE_TYPE
>(0x00),
static_cast<BYTE_TYPE
>(0x00) };
335 static constexpr auto bom_for_utf_ebcdic = std::array{
static_cast<BYTE_TYPE
>(0xDD),
static_cast<BYTE_TYPE
>(0x73),
static_cast<BYTE_TYPE
>(0x66),
static_cast<BYTE_TYPE
>(0x73) };
336 static constexpr auto bom_for_utf8 = std::array{
static_cast<BYTE_TYPE
>(0xEF),
static_cast<BYTE_TYPE
>(0xBB),
static_cast<BYTE_TYPE
>(0xBF) };
337 static constexpr auto bom_for_utf7 = std::array{
static_cast<BYTE_TYPE
>(0x2B),
static_cast<BYTE_TYPE
>(0x2F),
static_cast<BYTE_TYPE
>(0x76) };
338 static constexpr auto bom_for_utf1 = std::array{
static_cast<BYTE_TYPE
>(0xF7),
static_cast<BYTE_TYPE
>(0x64),
static_cast<BYTE_TYPE
>(0x4C) };
339 static constexpr auto bom_for_scsu = std::array{
static_cast<BYTE_TYPE
>(0x0E),
static_cast<BYTE_TYPE
>(0xFE),
static_cast<BYTE_TYPE
>(0xFF) };
340 static constexpr auto bom_for_bocu1 = std::array{
static_cast<BYTE_TYPE
>(0xFB),
static_cast<BYTE_TYPE
>(0xEE),
static_cast<BYTE_TYPE
>(0x28) };
341 static constexpr auto bom_for_utf16_be = std::array{
static_cast<BYTE_TYPE
>(0xFE),
static_cast<BYTE_TYPE
>(0xFF) };
342 static constexpr auto bom_for_utf16_le = std::array{
static_cast<BYTE_TYPE
>(0xFF),
static_cast<BYTE_TYPE
>(0xFE) };
346 using enum std::endian;
348 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_gb18030 })) { sv = sv.subspan(bom_for_gb18030.size());
return {
gb18030, native }; }
349 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf32_be })) { sv = sv.subspan(bom_for_utf32_be.size());
return { utf32, big }; }
350 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf32_le })) { sv = sv.subspan(bom_for_utf32_le.size());
return { utf32, little }; }
351 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf_ebcdic })) { sv = sv.subspan(bom_for_utf_ebcdic.size());
return {
utf_ebcdic, native }; }
352 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf8 })) { sv = sv.subspan(bom_for_utf8.size());
return { utf8, native }; }
353 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf7 })) { sv = sv.subspan(bom_for_utf7.size());
return { utf7, native }; }
354 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf1 })) { sv = sv.subspan(bom_for_utf1.size());
return {
utf1, native }; }
355 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_scsu })) { sv = sv.subspan(bom_for_scsu.size());
return {
scsu, native }; }
356 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_bocu1 })) { sv = sv.subspan(bom_for_bocu1.size());
return {
bocu1, native }; }
357 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf16_be })) { sv = sv.subspan(bom_for_utf16_be.size());
return { utf16, big }; }
358 if (starts_with(sv, std::span<BYTE_TYPE const>{ bom_for_utf16_le })) { sv = sv.subspan(bom_for_utf16_le.size());
return { utf16, little }; }
365 static constexpr std::string_view bom_for_gb18030{
"\x84\x31\x95\x33", 4 };
366 static constexpr std::string_view bom_for_utf32_be{
"\x00\x00\xFE\xFF", 4 };
367 static constexpr std::string_view bom_for_utf32_le{
"\xFF\xFE\x00\x00", 4 };
368 static constexpr std::string_view bom_for_utf_ebcdic{
"\xDD\x73\x66\x73", 4 };
369 static constexpr std::string_view bom_for_utf8{
"\xEF\xBB\xBF", 3 };
370 static constexpr std::string_view bom_for_utf7{
"\x2B\x2F\x76", 3 };
371 static constexpr std::string_view bom_for_utf1{
"\xF7\x64\x4C", 3 };
372 static constexpr std::string_view bom_for_scsu{
"\x0E\xFE\xFF", 3 };
373 static constexpr std::string_view bom_for_bocu1{
"\xFB\xEE\x28", 3 };
374 static constexpr std::string_view bom_for_utf16_be{
"\xFE\xFF", 2 };
375 static constexpr std::string_view bom_for_utf16_le{
"\xFF\xFE", 2 };
379 using enum std::endian;
381 if (sv.starts_with(bom_for_gb18030)) { sv.remove_prefix(bom_for_gb18030.size());
return {
gb18030, native }; }
382 if (sv.starts_with(bom_for_utf32_be)) { sv.remove_prefix(bom_for_utf32_be.size());
return { utf32, big }; }
383 if (sv.starts_with(bom_for_utf32_le)) { sv.remove_prefix(bom_for_utf32_le.size());
return { utf32, little }; }
384 if (sv.starts_with(bom_for_utf_ebcdic)) { sv.remove_prefix(bom_for_utf_ebcdic.size());
return {
utf_ebcdic, native }; }
385 if (sv.starts_with(bom_for_utf8)) { sv.remove_prefix(bom_for_utf8.size());
return { utf8, native }; }
386 if (sv.starts_with(bom_for_utf7)) { sv.remove_prefix(bom_for_utf7.size());
return { utf7, native }; }
387 if (sv.starts_with(bom_for_utf1)) { sv.remove_prefix(bom_for_utf1.size());
return {
utf1, native }; }
388 if (sv.starts_with(bom_for_scsu)) { sv.remove_prefix(bom_for_scsu.size());
return {
scsu, native }; }
389 if (sv.starts_with(bom_for_bocu1)) { sv.remove_prefix(bom_for_bocu1.size());
return {
bocu1, native }; }
390 if (sv.starts_with(bom_for_utf16_be)) { sv.remove_prefix(bom_for_utf16_be.size());
return { utf16, big }; }
391 if (sv.starts_with(bom_for_utf16_le)) { sv.remove_prefix(bom_for_utf16_le.size());
return { utf16, little }; }
398 static constexpr std::string_view bom_for_utf16_be{
"\xFE\xFF", 2 };
399 static constexpr std::string_view bom_for_utf16_le{
"\xFF\xFE", 2 };
403 const auto as_bytes = std::string_view{ (
char*)sv.data(), sv.size() *
sizeof(char16_t) };
405 if (
as_bytes.starts_with(bom_for_utf16_be)) { sv.remove_prefix(1);
return { text_encoding_type::utf16, std::endian::big }; }
406 if (
as_bytes.starts_with(bom_for_utf16_le)) { sv.remove_prefix(1);
return { text_encoding_type::utf16, std::endian::little }; }
413 static constexpr std::string_view bom_for_utf32_be{
"\xFF\xFE\x00\x00", 4 };
414 static constexpr std::string_view bom_for_utf32_le{
"\x00\x00\xFE\xFF", 4 };
418 const auto as_bytes = std::string_view{ (
char*)sv.data(), sv.size() *
sizeof(char32_t) };
420 if (
as_bytes.starts_with(bom_for_utf32_be)) { sv.remove_prefix(1);
return { text_encoding_type::utf32, std::endian::big }; }
421 if (
as_bytes.starts_with(bom_for_utf32_le)) { sv.remove_prefix(1);
return { text_encoding_type::utf32, std::endian::little }; }
429 enum status : uint8_t {
430 unsupported_encoding,
437 status status = unsupported_encoding;
439 char32_t point =
static_cast<char32_t>(-1);
451 basic_multilingual_plane,
452 supplementary_multilingual_plane,
453 supplementary_ideographic_plane,
454 tertiary_ideographic_plane,
455 supplementary_special_purpose_plane = 14,
459 bmp = basic_multilingual_plane,
460 smp = supplementary_multilingual_plane,
461 sip = supplementary_ideographic_plane,
462 tip = tertiary_ideographic_plane,
463 ssp = supplementary_special_purpose_plane,
464 spua_a = private_use_plane_a, pup_a = spua_a,
465 spua_b = private_use_plane_b, pup_b = spua_b,
468 constexpr inline char32_t last_unicode_code_point = 0x10FFFF;
469 constexpr inline char32_t first_unicode_high_surrogate = 0xD800;
470 constexpr inline char32_t last_unicode_high_surrogate = 0xDBFF;
471 constexpr inline char32_t first_unicode_low_surrogate = 0xDC00;
472 constexpr inline char32_t last_unicode_low_surrogate = 0xDFFF;
474 constexpr bool is_high_surrogate(
char32_t cp)
noexcept {
return cp >= first_unicode_high_surrogate && cp <= last_unicode_high_surrogate; }
475 constexpr bool is_low_surrogate(
char32_t cp)
noexcept {
return cp >= first_unicode_low_surrogate && cp <= last_unicode_low_surrogate; }
476 constexpr bool is_surrogate(
char32_t cp)
noexcept {
return cp >= first_unicode_high_surrogate && cp <= last_unicode_low_surrogate; }
477 constexpr bool is_unicode(
char32_t cp)
noexcept {
return cp <= last_unicode_code_point; }
483 return 0x10000 + ((high - first_unicode_high_surrogate) << 10) + (low - first_unicode_low_surrogate);
486 constexpr std::pair<char32_t, char32_t> codepoint_to_surrogate_pair(
char32_t cp)
noexcept
488 return { ((cp - 0x10000) >> 10) + first_unicode_high_surrogate, ((cp - 0x10000) & 0x3FF) + first_unicode_low_surrogate };
493 template <std::
integral T>
494 T get_val(
const void* source, std::endian source_endianness)
497 std::memcpy(&result, source,
sizeof(T));
498 if (std::endian::native != source_endianness)
499 return byteswap(result);
506 constexpr size_t lut[33] = { 7,6,6,6,6,6,5,5,5,5,5,4,4,4,4,4,3,3,3,3,3,2,2,2,2,1,1,1,1,1,1,1,1 };
507 return lut[std::countl_zero(std::bit_cast<uint32_t>(cp))];
512 auto sv = make_sv(str);
519 o = std::bit_cast<uint8_t>(sv.back());
522 }
while ((o & 0xC0) == 0x80);
528 auto sv = make_sv(_str);
530 return { text_decode_result::truncated };
532 switch (encoding.type)
534 case text_encoding_type::utf8:
536 const auto first = std::bit_cast<uint8_t>(sv[0]);
538 return { text_decode_result::valid, first, 1 };
542 if ((first >> 5) == 0x6) { length = 2; value = first & 0x1F; }
543 else if ((first >> 4) == 0xe) { length = 3; value = first & 0xF; }
544 else if ((first >> 3) == 0x1e) { length = 4; value = first & 0x7; }
546 if (length == 0 || sv.size() < length)
547 return { text_decode_result::truncated, first, 1 };
552 if ((sv[3] & 0xC0) != 0x80)
553 return { text_decode_result::invalid, first, 1 };
554 value = (value << 6) | (sv[3] & 0x3f);
557 if ((sv[2] & 0xC0) != 0x80)
558 return { text_decode_result::invalid, first, 1 };
559 value = (value << 6) | (sv[2] & 0x3f);
562 if ((sv[1] & 0xC0) != 0x80)
563 return { text_decode_result::invalid, first, 1 };
564 value = (value << 6) | (sv[1] & 0x3f);
565 return { text_decode_result::valid, value, length };
569 case text_encoding_type::utf16:
572 return { text_decode_result::truncated };
574 const uint16_t first = detail::get_val<uint16_t>(sv.data(), encoding.endianness);
580 return { text_decode_result::truncated, first, 2 };
582 const uint16_t second = detail::get_val<uint16_t>(sv.data() + 2, encoding.endianness);
587 return { text_decode_result::invalid, first, 2 };
590 return { text_decode_result::valid, first, 2 };
592 case text_encoding_type::utf32:
595 return { text_decode_result::truncated };
597 const uint32_t cp = detail::get_val<uint32_t>(sv.data(), encoding.endianness);
599 return { text_decode_result::valid, cp, 4 };
600 return { text_decode_result::invalid, cp, 4 };
607 template <
bytelike_range T>
610 struct TextFileStats {
612 size_t valid_points = 0;
613 size_t control_points = 0;
614 size_t plain_ascii = 0;
615 size_t whitespace = 0;
616 size_t extended_codepoints = 0;
617 float one_over_points = 0.f;
618 size_t invalid_points()
const {
return points - valid_points; }
619 float score()
const {
return (2.5f * whitespace + plain_ascii - 100.f * invalid_points() - 50.f * control_points + 5.f * extended_codepoints) * one_over_points; }
627 if (decoded.status == text_decode_result::truncated)
632 if (decoded.status == text_decode_result::valid)
634 stats.valid_points++;
635 if (decoded.
point < 32)
637 if (decoded.
point ==
'\n' || decoded.
point ==
'\t')
642 else if (decoded.
point ==
'\r')
645 stats.control_points++;
647 else if (decoded.
point < 127)
650 if (decoded.
point ==
' ')
653 else if (decoded.
point >= 65536)
654 stats.extended_codepoints++;
657 if (stats.points > 0)
658 stats.one_over_points = 1.f / stats.points;
662 auto sv = make_sv(str);
670 sv = sv.substr(0, 4096);
672 TextFileStats stats8;
675 size_t numBytesRead = calculate_stats(stats8, sv, utf8_encoding);
676 if (numBytesRead == 0)
677 return utf8_encoding;
680 if (stats8.invalid_points() == 0 && stats8.control_points == 0)
681 return utf8_encoding;
686 if (
const auto numHighBytes = numBytesRead - stats8.plain_ascii - stats8.control_points;
687 stats8.invalid_points() >= numHighBytes * 0.2f)
691 stats8.points = numBytesRead;
692 stats8.valid_points = numBytesRead;
696 TextFileStats stats16_le;
697 calculate_stats(stats16_le, sv, utf16_le_encoding);
699 TextFileStats stats16_be;
700 calculate_stats(stats16_be, sv, utf16_be_encoding);
703 TextFileStats* stats16 = &stats16_le;
705 if (stats16_be.score() > stats16_le.score())
707 stats16 = &stats16_be;
708 encoding16 = utf16_be_encoding;
712 TextFileStats stats32_le;
713 calculate_stats(stats32_le, sv, utf32_le_encoding);
715 TextFileStats stats32_be;
716 calculate_stats(stats32_be, sv, utf32_be_encoding);
719 TextFileStats* stats32 = &stats32_le;
721 if (stats32_be.score() > stats32_le.score())
723 stats32 = &stats32_be;
724 encoding32 = utf32_be_encoding;
728 const auto score8 = stats8.score();
729 const auto score16 = stats16->score();
730 const auto score32 = stats32->score();
731 if (score8 >= score32)
733 if (score16 >= score8)
738 if (score32 >= score16)
743 [[gsl::suppress(
"type.1",
"es.79")]]
746 using char_type =
typename std::remove_cvref_t<
decltype(str)>::value_type;
747 using unsigned_char_type = std::make_unsigned_t<char_type>;
749 if (str.empty())
return 0;
750 auto it = std::to_address(str.begin());
751 char32_t cp =
static_cast<unsigned_char_type
>(*it);
754 if (cp < 0x80) length = 1;
755 else if ((cp >> 5) == 0x6) length = 2;
756 else if ((cp >> 4) == 0xe) length = 3;
757 else if ((cp >> 3) == 0x1e) length = 4;
762 ++it; cp = ((cp << 6) & 0x7ff) + (static_cast<unsigned_char_type>(*it) & 0x3f);
765 ++it; cp = ((cp << 12) & 0xffff) + ((static_cast<unsigned_char_type>(*it) << 6) & 0xfff);
766 ++it; cp +=
static_cast<unsigned_char_type
>(*it) & 0x3f;
769 ++it; cp = ((cp << 18) & 0x1fffff) + ((static_cast<unsigned_char_type>(*it) << 12) & 0x3ffff);
770 ++it; cp += (
static_cast<unsigned_char_type
>(*it) << 6) & 0xfff;
771 ++it; cp +=
static_cast<unsigned_char_type
>(*it) & 0x3f;
774 str.remove_prefix(length);
778 [[gsl::suppress(
"type.1",
"es.79")]]
781 using char_type =
typename std::remove_cvref_t<
decltype(str)>::value_type;
782 using unsigned_char_type = std::make_unsigned_t<char_type>;
784 if (str.empty())
return 0;
785 auto it = std::to_address(str.begin());
786 char32_t cp =
static_cast<unsigned_char_type
>(*it);
789 if (cp < 0x80) length = 1;
790 else if ((cp >> 5) == 0x6) length = 2;
791 else if ((cp >> 4) == 0xe) length = 3;
792 else if ((cp >> 3) == 0x1e) length = 4;
797 ++it; cp = ((cp << 6) & 0x7ff) + (static_cast<unsigned_char_type>(*it) & 0x3f);
800 ++it; cp = ((cp << 12) & 0xffff) + ((static_cast<unsigned_char_type>(*it) << 6) & 0xfff);
801 ++it; cp +=
static_cast<unsigned_char_type
>(*it) & 0x3f;
804 ++it; cp = ((cp << 18) & 0x1fffff) + ((static_cast<unsigned_char_type>(*it) << 12) & 0x3ffff);
805 ++it; cp += (
static_cast<unsigned_char_type
>(*it) << 6) & 0xfff;
806 ++it; cp +=
static_cast<unsigned_char_type
>(*it) & 0x3f;
814 auto str = make_sv(_str);
815 using char_type =
typename std::remove_cvref_t<
decltype(str)>::value_type;
816 using unsigned_char_type = std::make_unsigned_t<char_type>;
818 auto it = std::to_address(str.begin());
819 const auto end = std::to_address(str.end());
824 char32_t cp =
static_cast<unsigned_char_type
>(*it);
827 if ((cp >> 5) == 0x6) length = 2;
828 else if ((cp >> 4) == 0xe) length = 3;
829 else if ((cp >> 3) == 0x1e) length = 4;
839 auto str = make_sv(_str);
840 using char_type =
typename std::remove_cvref_t<
decltype(str)>::value_type;
841 using unsigned_char_type = std::make_unsigned_t<char_type>;
843 auto it = std::to_address(str.begin());
844 const auto end = std::to_address(str.end());
848 char32_t cp =
static_cast<unsigned_char_type
>(*it);
850 if ((cp >> 5) == 0x6)
return 2;
851 else if ((cp >> 4) == 0xe)
return 3;
852 else if ((cp >> 3) == 0x1e)
return 4;
865 str.resize(str.size() - size);
874 result =
peek_utf8(str.substr(str.size() - size));
875 str.remove_suffix(size);
884 result =
peek_utf8(str.substr(str.size() - size));
888 [[gsl::suppress(
"type.1")]]
891 using char_type =
typename std::remove_cvref_t<
decltype(buffer)>::value_type;
894 std::decay_t<
decltype(buffer)> bytes(cp_bytes, 0);
897 case 7: bytes[cp_bytes - 6] =
static_cast<char_type>(0x80 | ((cp >> 30) & 0x3F)); [[fallthrough]];
898 case 6: bytes[cp_bytes - 5] =
static_cast<char_type>(0x80 | ((cp >> 24) & 0x3F)); [[fallthrough]];
899 case 5: bytes[cp_bytes - 4] =
static_cast<char_type>(0x80 | ((cp >> 18) & 0x3F)); [[fallthrough]];
900 case 4: bytes[cp_bytes - 3] =
static_cast<char_type>(0x80 | ((cp >> 12) & 0x3F)); [[fallthrough]];
901 case 3: bytes[cp_bytes - 2] =
static_cast<char_type>(0x80 | ((cp >> 6) & 0x3F)); [[fallthrough]];
902 case 2: bytes[cp_bytes - 1] =
static_cast<char_type>(0x80 | ((cp >> 0) & 0x3F)); bytes[0] =
static_cast<char_type>((std::uint_least16_t(0xFF00uL) >> cp_bytes) | (uint64_t(cp) >> (6 * cp_bytes - 6)));
break;
903 case 1: bytes[0] =
static_cast<char_type>(cp);
break;
915 buffer +=
static_cast<char_type>((cp >> 6) | 0xc0);
916 buffer +=
static_cast<char_type>((cp & 0x3f) | 0x80);
919 else if (cp < 0x10000)
921 buffer +=
static_cast<char_type>((cp >> 12) | 0xe0);
922 buffer +=
static_cast<char_type>(((cp >> 6) & 0x3f) | 0x80);
923 buffer +=
static_cast<char_type>((cp & 0x3f) | 0x80);
928 buffer +=
static_cast<char_type>((cp >> 18) | 0xf0);
929 buffer +=
static_cast<char_type>(((cp >> 12) & 0x3f) | 0x80);
930 buffer +=
static_cast<char_type>(((cp >> 6) & 0x3f) | 0x80);
931 buffer +=
static_cast<char_type>((cp & 0x3f) | 0x80);
939 using dest_char =
typename std::decay_t<
decltype(dest)>::value_type;
940 for (uint8_t cp : make_sv(source))
943 dest +=
static_cast<dest_char
>(cp);
945 append_utf8(dest, codepage_map[
static_cast<size_t>(cp) - 0x80]);
949 template <
typename T>
952 using dest_char =
typename std::decay_t<
decltype(dest)>::value_type;
953 for (uint8_t cp : make_sv(source))
956 dest +=
static_cast<dest_char
>(cp);
962 template <
typename RESULT>
978 [[gsl::suppress(
"type.1",
"es.79")]]
981 using char_type =
typename std::remove_cvref_t<
decltype(str)>::value_type;
982 using unsigned_char_type = std::make_unsigned_t<char_type const>;
984 if (str.empty())
return 0;
985 auto it = (unsigned_char_type*)std::to_address(str.begin());
988 const int length = int(cp >= 0xD800 && cp <= 0xDBFF) + 1;
993 cp = ((cp - 0xD800) << 10) | (*it - 0xDC00);
995 str.remove_prefix(length);
1001 if (str.empty())
return 0;
1002 const auto result = str[0];
1003 str.remove_prefix(1);
1007 [[gsl::suppress(
"type.1")]]
1010 using char_type =
typename std::remove_cvref_t<
decltype(buffer)>::value_type;
1017 buffer +=
static_cast<char_type>((cp >> 10) + 0xD800);
1018 buffer +=
static_cast<char_type>((cp & 0x3FF) + 0xDC00);
1028 template <
string8 T>
1029 [[gsl::suppress(
"type.1")]]
1032 using char_type =
typename T::value_type;
1035 else if (cp < 0x800)
1036 return {
static_cast<char_type>((cp >> 6) | 0xc0),
static_cast<char_type>((cp & 0x3f) | 0x80) };
1037 else if (cp < 0x10000)
1038 return {
static_cast<char_type>((cp >> 12) | 0xe0),
static_cast<char_type>(((cp >> 6) & 0x3f) | 0x80),
static_cast<char_type>((cp & 0x3f) | 0x80) };
1040 return {
static_cast<char_type>((cp >> 18) | 0xf0),
static_cast<char_type>(((cp >> 12) & 0x3f) | 0x80),
static_cast<char_type>(((cp >> 6) & 0x3f) | 0x80),
static_cast<char_type>((cp & 0x3f) | 0x80) };
1043 template <
string8 RESULT,
stringable8 STR>
1044 [[nodiscard]]
constexpr RESULT
to_utf8(STR&& str)
1046 if constexpr (std::same_as<STR, RESULT>)
1047 return std::forward<STR>(str);
1050 using char_type =
typename RESULT::value_type;
1055 template <
string8 RESULT,
stringable16 STR>
1059 auto sv = make_sv(str);
1066 template <
string8 RESULT,
stringable32 STR>
1070 auto sv = make_sv(str);
1077 [[nodiscard]]
inline std::string to_string(std::wstring_view str)
1082 template <
string16 T>
1083 [[gsl::suppress(
"type.1")]]
1090 return {
static_cast<char_type>((cp >> 10) + 0xD800),
static_cast<char_type>((cp & 0x3FF) + 0xDC00) };
1093 template <
string16 T,
stringable8 STR>
1097 auto sv = make_sv(str);
1103 [[nodiscard]]
inline std::wstring
to_wstring(std::string_view str)
1108 template <std::ranges::view R>
1109 struct utf8_view :
public std::ranges::view_interface<utf8_view<R>>
1111 template <
typename RANGE_ITER,
typename SENTINEL>
1112 struct utf8_iterator
1114 using iterator_category = std::forward_iterator_tag;
1115 using value_type = char32_t;
1116 using difference_type = ptrdiff_t;
1117 using reference = char32_t;
1119 constexpr utf8_iterator(RANGE_ITER current, SENTINEL end) : mCurrent(std::move(current)), mEnd(std::move(end)) {}
1121 [[nodiscard]]
constexpr value_type operator*()
const {
1122 const auto length = len();
1124 char32_t cp = std::bit_cast<uint8_t>(*it);
1128 ++it; cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);
1131 ++it; cp = ((cp << 12) & 0xffff) + ((*it << 6) & 0xfff);
1132 ++it; cp += (*it) & 0x3f;
1135 ++it; cp = ((cp << 18) & 0x1fffff) + (((*it) << 12) & 0x3ffff);
1136 ++it; cp += ((*it) << 6) & 0xfff;
1137 ++it; cp += (*it) & 0x3f;
1143 constexpr utf8_iterator& operator++() {
1144 std::advance(mCurrent, len());
1148 constexpr utf8_iterator operator++(
int) {
1154 constexpr auto operator<=>(utf8_iterator
const&)
const noexcept =
default;
1160 if (mCurrent >= mEnd)
1161 throw std::out_of_range(
"utf8 iterator out of range");
1163 const unsigned cp = std::bit_cast<uint8_t>(*mCurrent);
1165 if (cp < 0x80) length = 1;
1166 else if ((cp >> 5) == 0x6) length = 2;
1167 else if ((cp >> 4) == 0xe) length = 3;
1168 else if ((cp >> 3) == 0x1e) length = 4;
1170 throw std::runtime_error(
"invalid utf-8 prefix");
1172 if (mCurrent + length > mEnd)
1173 throw std::runtime_error(
"utf-8 range contains codepoint with length beyond end of range");
1178 RANGE_ITER mCurrent;
1182 utf8_view() =
default;
1184 constexpr utf8_view(R base)
1189 template <
typename... ARGS>
1190 requires std::constructible_from<std::string_view, ARGS...>
1191 explicit constexpr utf8_view(ARGS&&... args)
1192 : mBase(std::forward<ARGS>(args)...)
1196 constexpr R base()
const&
1200 constexpr R base()&&
1202 return std::move(mBase);
1205 constexpr auto begin()
const
1207 return utf8_iterator<
decltype(std::begin(mBase)),
decltype(std::end(mBase))>{std::begin(mBase), std::end(mBase)};
1210 constexpr auto end()
const
1212 return utf8_iterator<
decltype(std::end(mBase)),
decltype(std::end(mBase))>{std::end(mBase), std::end(mBase)};
Whether the type is a native char type.
The type is a string with a 16-bit char type.
The type is a string with an 32-bit char type.
The type is a string with an 8-bit char type.
The type is a string view with a 16-bit char type.
The type is a string view with a 32-bit char type.
The type is a string view with an 8-bit char type.
The type is convertible to a string view with a 16-bit char type.
The type is convertible to a string view with a 32-bit char type.
The type is convertible to a string view with an 8-bit char type.
auto as_bytes(std::span< FROM, N > bytes) noexcept
Returns a span of std::bytes that represents the bytelikes in bytes.
constexpr std::basic_string_view< COUT > string_view_cast(std::basic_string_view< CIN > id) noexcept
Casts a string_view to a string_view with a different char type via a simple reinterpret_cast.
std::string_view substr(std::string_view str, intptr_t start, size_t count=std::string::npos) noexcept
Gets a substring of str starting at start and containing count characters.
std::string to_string(T floating_point)
float overload
unicode_plane
Represents the Unicode plane.
constexpr char32_t consume_utf32(string_view32 auto &str)
Consumes (see consume()) a UTF-32 codepoint from str.
text_encoding_type
Specifies a base text-encoding, ignoring endianness for multi-byte encodings.
constexpr void transcode_unicode(FROM const &from, TO &out)
Converts a UTF-encoded string to a UTF-encoded string, of a different encoding. Decides the encodings...
constexpr size_t codepoint_utf8_count_from_end(stringable8 auto str)
Count the number of octets in the last UTF-8 character in the string.
constexpr char32_t peek_utf8(string_view8 auto str)
Decodes the first UTF-8 codepoint in str.
constexpr void transcode_codepage_to_unicode(T &dest, stringable8 auto source, std::span< char32_t const, 128 > codepage_map)
Transcodes an Extended ASCII string source into unicode-encoded dest, according to codepage_map.
constexpr bool is_unicode_character(char32_t cp) noexcept
Returns whether cp has a value that is a valid Unicode character (ie.
constexpr size_t append_utf32(string32 auto &buffer, char32_t cp)
Appends 32-bit values to buffer by encoding cp into UTF-32.
constexpr char32_t surrogate_pair_to_codepoint(char32_t high, char32_t low) noexcept
Returns the codepoint encoded by two surrogates.
constexpr size_t count_utf8_codepoints(stringable8 auto str)
Returns the number of codepoints in the given UTF-8 string str.
constexpr bool is_unicode(char32_t cp) noexcept
Returns whether cp has a value that is a valid Unicode codepoint (ie. between 0 and 0x10FFFF).
constexpr void transcode_codepage_to_utf8(string8 auto &dest, stringable8 auto source, std::span< char32_t const, 128 > codepage_map)
Transcodes an Extended ASCII string source into UTF-8 dest, according to codepage_map.
constexpr size_t append_utf8(string8 auto &buffer, char32_t cp)
Appends octets to buffer by encoding cp into UTF-8.
constexpr RESULT to_utf8(char32_t cp)
Returns cp encoded as a UTF-8 string.
constexpr char32_t consume_utf16(string_view16 auto &str)
Consumes (see consume()) a UTF-16 codepoint from str.
constexpr RESULT to_utf16(char32_t cp)
Returns cp encoded as a UTF-16 string.
constexpr bool is_surrogate(char32_t cp) noexcept
Returns whether cp is a codepoint that encodes any part of a codepoint with a more-than-16-bit value.
constexpr char32_t consume_utf8_at_end(string_view8 auto &str)
Consumes (see consume()) the last UTF-8 codepoint in str.
constexpr void append_codepoint(T &str, char32_t cp)
Appends a codepoint to a UTF-encoded string. Supports UTF-8, UTF-16 and UTF-32, decides based on char...
constexpr char32_t consume_codepoint(T &str)
Consumes a codepoint from a UTF-encoded string and returns it.
constexpr char32_t consume_utf8(string_view8 auto &str)
Consumes (see consume()) a UTF-8 codepoint from str.
constexpr size_t codepoint_utf8_count(char32_t cp) noexcept
Returns the number of UTF-8 octets necessarity to encode the given codepoint.
constexpr bool is_high_surrogate(char32_t cp) noexcept
Returns whether cp is a codepoint that encodes the high part of a codepoint with a more-than-16-bit v...
text_encoding detect_encoding(T const &range)
Attempts to detect the encoding of a given bytelike range.
constexpr char32_t peek_utf8_at_end(string_view8 auto str)
Decodes the last UTF-8 codepoint in str.
constexpr size_t append_utf16(string16 auto &buffer, char32_t cp)
Appends 16-bit values to buffer by encoding cp into UTF-16.
text_decode_result decode_codepoint(bytelike_range auto range, text_encoding encoding)
Attempts to decode the first codepoint in bytelike range range, assuming it is encoded in encoding.
void encode_flag(std::string_view ascii_country_code, FUNC &&encoding_func)
constexpr text_encoding unknown_text_encoding
Represents an unknown text encoding (e.g. when an encoding could not be determined).
std::wstring to_wstring(std::string_view str)
Returns str (a UTF-8-encoded string) encoded as a UTF-16/32 string in a std::wstring (depending on th...
constexpr bool is_low_surrogate(char32_t cp) noexcept
Returns whether cp is a codepoint that encodes the low part of a codepoint with a more-than-16-bit va...
text_encoding consume_bom(std::span< BYTE_TYPE, N > &spn)
Consumes (see consume()) a byte order mark from the beginning of spn (a span of bytelike),...
constexpr size_t first_utf8_codepoint_size(stringable8 auto str)
Returns the size in octets of the first UTF-8 codepoint in str.
constexpr char32_t pop_utf8(string8 auto &str)
Removes the last UTF-8 codepoint from str.
@ invalid
Represents an invalid plane number.
@ utf1
https://en.wikipedia.org/wiki/UTF-7
@ utf_ebcdic
https://en.wikipedia.org/wiki/UTF-1
@ gb18030
https://en.wikipedia.org/wiki/Binary_Ordered_Compression_for_Unicode
@ scsu
https://en.wikipedia.org/wiki/UTF-EBCDIC
@ bocu1
https://en.wikipedia.org/wiki/Standard_Compression_Scheme_for_Unicode
Shamelessly stolen from https://github.com/arc80/plywood/.
uint8_t byte_count
The number of bytes this codepoint takes up in the input string.
char32_t point
The decoded codepoint (or -1 if failed).
Type that represents a specific text encoding - a combination of ghassanpl::string_ops::text_encoding...