src/internal/utf8_conversion.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_INTERNAL_UTF8_CONVERSION_HPP | ||
| 2 | #define DETOURMODKIT_INTERNAL_UTF8_CONVERSION_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file utf8_conversion.hpp | ||
| 6 | * @brief UTF-8 and UTF-16 conversion for paths and names. No conversion uses the ANSI code page. | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <windows.h> | ||
| 10 | |||
| 11 | #include <climits> | ||
| 12 | #include <cstddef> | ||
| 13 | #include <string> | ||
| 14 | #include <string_view> | ||
| 15 | |||
| 16 | namespace DetourModKit | ||
| 17 | { | ||
| 18 | namespace detail | ||
| 19 | { | ||
| 20 | /** | ||
| 21 | * @brief Widens strict UTF-8 text to UTF-16. | ||
| 22 | * @details Reads exactly @c text.size() bytes, so a view without a terminator is safe. | ||
| 23 | * @param text The UTF-8 text. | ||
| 24 | * @return The UTF-16 text. The result is empty for empty input, an embedded NUL, a size over INT_MAX, or | ||
| 25 | * ill-formed UTF-8. | ||
| 26 | * @throws std::bad_alloc when the result cannot be allocated. | ||
| 27 | */ | ||
| 28 | 4483 | [[nodiscard]] inline std::wstring widen_utf8(std::string_view text) | |
| 29 | { | ||
| 30 | 8962 | if (text.empty() || text.size() > static_cast<std::size_t>(INT_MAX) || | |
| 31 | 4479 | text.find('\0') != std::string_view::npos) | |
| 32 | { | ||
| 33 | 7 | return std::wstring{}; | |
| 34 | } | ||
| 35 | |||
| 36 | 4475 | const int input_length = static_cast<int>(text.size()); | |
| 37 | const int wide_length = | ||
| 38 | 4475 | ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.data(), input_length, nullptr, 0); | |
| 39 | 4475 | if (wide_length <= 0) | |
| 40 | { | ||
| 41 | 13 | return std::wstring{}; | |
| 42 | } | ||
| 43 | 4465 | std::wstring wide(static_cast<std::size_t>(wide_length), L'\0'); | |
| 44 | 4460 | const int converted = ::MultiByteToWideChar( | |
| 45 | CP_UTF8, | ||
| 46 | MB_ERR_INVALID_CHARS, | ||
| 47 | text.data(), | ||
| 48 | input_length, | ||
| 49 | wide.data(), | ||
| 50 | wide_length | ||
| 51 | ); | ||
| 52 | 4460 | if (converted != wide_length) | |
| 53 | { | ||
| 54 | ✗ | return std::wstring{}; | |
| 55 | } | ||
| 56 | 4460 | return wide; | |
| 57 | 4460 | } | |
| 58 | |||
| 59 | /** | ||
| 60 | * @brief Renders UTF-16 text as UTF-8. | ||
| 61 | * @details An unpaired surrogate becomes U+FFFD, so the conversion does not fail on content. | ||
| 62 | * @param wide The UTF-16 text. | ||
| 63 | * @return The UTF-8 text. The result is empty for empty input or a size over INT_MAX. | ||
| 64 | * @throws std::bad_alloc when the result cannot be allocated. | ||
| 65 | */ | ||
| 66 | 969 | [[nodiscard]] inline std::string utf8_from_wide(std::wstring_view wide) | |
| 67 | { | ||
| 68 | 969 | if (wide.empty() || wide.size() > static_cast<std::size_t>(INT_MAX)) | |
| 69 | { | ||
| 70 | 4 | return std::string{}; | |
| 71 | } | ||
| 72 | |||
| 73 | 965 | const int input_length = static_cast<int>(wide.size()); | |
| 74 | const int byte_length = | ||
| 75 | 965 | ::WideCharToMultiByte(CP_UTF8, 0, wide.data(), input_length, nullptr, 0, nullptr, nullptr); | |
| 76 | 965 | if (byte_length <= 0) | |
| 77 | { | ||
| 78 | ✗ | return std::string{}; | |
| 79 | } | ||
| 80 | 965 | std::string text(static_cast<std::size_t>(byte_length), '\0'); | |
| 81 | 965 | const int converted = ::WideCharToMultiByte( | |
| 82 | CP_UTF8, | ||
| 83 | 0, | ||
| 84 | wide.data(), | ||
| 85 | input_length, | ||
| 86 | text.data(), | ||
| 87 | byte_length, | ||
| 88 | nullptr, | ||
| 89 | nullptr | ||
| 90 | ); | ||
| 91 | 965 | if (converted != byte_length) | |
| 92 | { | ||
| 93 | ✗ | return std::string{}; | |
| 94 | } | ||
| 95 | 965 | return text; | |
| 96 | 965 | } | |
| 97 | } // namespace detail | ||
| 98 | } // namespace DetourModKit | ||
| 99 | |||
| 100 | #endif // DETOURMODKIT_INTERNAL_UTF8_CONVERSION_HPP | ||
| 101 |