|
38 | 38 | #include <cstdlib> |
39 | 39 | #include <fstream> |
40 | 40 | #include <iostream> |
| 41 | +#include <iterator> |
| 42 | +#include <locale> |
41 | 43 | #include <mutex> |
42 | 44 | #include <optional> |
43 | 45 | #include <sstream> |
@@ -884,6 +886,69 @@ bool getenv_is_enabled(os_access const& os, char const* var) { |
884 | 886 | return false; |
885 | 887 | } |
886 | 888 |
|
| 889 | +namespace { |
| 890 | + |
| 891 | +#ifdef _WIN32 |
| 892 | + |
| 893 | +std::string wchar_to_utf8(wchar_t wc) { |
| 894 | + std::string out; |
| 895 | + if (wc != 0) { |
| 896 | + try { |
| 897 | + static_assert(sizeof(wchar_t) == 2); |
| 898 | + auto const cu = static_cast<char16_t>(wc); |
| 899 | + utf8::utf16to8(&cu, &cu + 1, std::back_inserter(out)); |
| 900 | + } catch (std::exception const&) { |
| 901 | + out.clear(); |
| 902 | + } |
| 903 | + } |
| 904 | + return out; |
| 905 | +} |
| 906 | + |
| 907 | +// `numpunct<char>` replacement that disables grouping for char streams. |
| 908 | +struct safe_numpunct : std::numpunct<char> { |
| 909 | + std::string do_grouping() const override { return {}; } |
| 910 | + char do_decimal_point() const override { return '.'; } |
| 911 | +}; |
| 912 | + |
| 913 | +void install_number_formatting_facets() { |
| 914 | + bool needs_wide_chars = true; // safe default |
| 915 | + |
| 916 | + // Now check if number formatting uses multi-byte UTF-8 characters. |
| 917 | + // If so, we can provide fmt with a custom facet that uses UTF-8 strings |
| 918 | + // based on the wide-character locale. |
| 919 | + try { |
| 920 | + auto const& np = std::use_facet<std::numpunct<wchar_t>>(std::locale()); |
| 921 | + auto const grouping = np.grouping(); |
| 922 | + |
| 923 | + // Empty grouping means "don't group" -> we're done. |
| 924 | + if (grouping.empty()) { |
| 925 | + needs_wide_chars = false; |
| 926 | + } else { |
| 927 | + auto const sep = wchar_to_utf8(np.thousands_sep()); |
| 928 | + auto dp = wchar_to_utf8(np.decimal_point()); |
| 929 | + |
| 930 | + needs_wide_chars = sep.size() > 1 || dp.size() > 1; |
| 931 | + |
| 932 | + if (needs_wide_chars) { |
| 933 | + std::locale::global(std::locale( |
| 934 | + std::locale(), |
| 935 | + new fmt::format_facet<std::locale>(sep, grouping, std::move(dp)))); |
| 936 | + } |
| 937 | + } |
| 938 | + } catch (std::exception const&) { |
| 939 | + // fall back to the safe default |
| 940 | + needs_wide_chars = true; |
| 941 | + } |
| 942 | + |
| 943 | + if (needs_wide_chars) { |
| 944 | + std::locale::global(std::locale(std::locale(), new safe_numpunct)); |
| 945 | + } |
| 946 | +} |
| 947 | + |
| 948 | +#endif |
| 949 | + |
| 950 | +} // namespace |
| 951 | + |
887 | 952 | void setup_default_locale() { |
888 | 953 | char const* const candidates[] = { |
889 | 954 | #ifdef _WIN32 |
@@ -919,6 +984,8 @@ void setup_default_locale() { |
919 | 984 | } |
920 | 985 |
|
921 | 986 | #ifdef _WIN32 |
| 987 | + install_number_formatting_facets(); |
| 988 | + |
922 | 989 | SetConsoleOutputCP(CP_UTF8); |
923 | 990 | #endif |
924 | 991 | } |
|
0 commit comments