diff options
Diffstat (limited to 'drafts')
-rw-r--r-- | drafts/String.cpp | 129 | ||||
-rw-r--r-- | drafts/String.hpp | 75 |
2 files changed, 204 insertions, 0 deletions
diff --git a/drafts/String.cpp b/drafts/String.cpp new file mode 100644 index 00000000..eb585523 --- /dev/null +++ b/drafts/String.cpp @@ -0,0 +1,129 @@ +#include "cru/win/String.hpp" + +#include "cru/win/Exception.hpp" + +#include <type_traits> + +namespace cru::platform::win { +std::string ToUtf8String(const std::wstring_view& string) { + if (string.empty()) return std::string{}; + + const auto length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), nullptr, 0, nullptr, nullptr); + if (length == 0) { + throw Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-8."); + } + + std::string result; + result.resize(length); + if (::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), result.data(), + static_cast<int>(result.size()), nullptr, + nullptr) == 0) + throw Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-8."); + return result; +} + +std::wstring ToUtf16String(const std::string_view& string) { + if (string.empty()) return std::wstring{}; + + const auto length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), nullptr, 0); + if (length == 0) { + throw Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-16."); + } + + std::wstring result; + result.resize(length); + if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), result.data(), + static_cast<int>(result.size())) == 0) + throw win::Win32Error(::GetLastError(), + "Failed to convert wide string to UTF-16."); + return result; +} + +template <typename UInt, int number_of_bit> +inline std::enable_if_t<std::is_unsigned_v<UInt>, CodePoint> ExtractBits( + UInt n) { + return static_cast<CodePoint>(n & ((1u << number_of_bit) - 1)); +} + +CodePoint Utf16Iterator::Next() { + if (position_ == static_cast<Index>(string_.length())) + return k_code_point_end; + + const auto cu0 = static_cast<std::uint16_t>(string_[position_++]); + + if (cu0 < 0xd800u || cu0 >= 0xe000u) { // 1-length code point + return static_cast<CodePoint>(cu0); + } else if (cu0 <= 0xdbffu) { // 2-length code point + if (position_ == static_cast<Index>(string_.length())) { + throw TextEncodeException( + "Unexpected end when reading second code unit of surrogate pair."); + } + const auto cu1 = static_cast<std::uint16_t>(string_[position_++]); + +#ifdef CRU_DEBUG + if (cu1 < 0xDC00u || cu1 > 0xdfffu) { + throw TextEncodeException( + "Unexpected bad-format second code unit of surrogate pair."); + } +#endif + + const auto s0 = ExtractBits<std::uint16_t, 10>(cu0) << 10; + const auto s1 = ExtractBits<std::uint16_t, 10>(cu1); + + return s0 + s1 + 0x10000; + + } else { + throw TextEncodeException( + "Unexpected bad-format first code unit of surrogate pair."); + } +} + +Index IndexUtf8ToUtf16(const std::string_view& utf8_string, Index utf8_index, + const std::wstring_view& utf16_string) { + if (utf8_index >= static_cast<Index>(utf8_string.length())) + return utf16_string.length(); + + Index cp_index = 0; + Utf8Iterator iter{utf8_string}; + while (iter.CurrentPosition() <= utf8_index) { + iter.Next(); + cp_index++; + } + + Utf16Iterator result_iter{utf16_string}; + for (Index i = 0; i < cp_index - 1; i++) { + if (result_iter.Next() == k_code_point_end) break; + } + + return result_iter.CurrentPosition(); +} + +Index IndexUtf16ToUtf8(const std::wstring_view& utf16_string, Index utf16_index, + const std::string_view& utf8_string) { + if (utf16_index >= static_cast<Index>(utf16_string.length())) + return utf8_string.length(); + + Index cp_index = 0; + Utf16Iterator iter{utf16_string}; + while (iter.CurrentPosition() <= utf16_index) { + iter.Next(); + cp_index++; + } + + Utf8Iterator result_iter{utf8_string}; + for (Index i = 0; i < cp_index - 1; i++) { + if (result_iter.Next() == k_code_point_end) break; + } + + return result_iter.CurrentPosition(); +} +} // namespace cru::platform::win diff --git a/drafts/String.hpp b/drafts/String.hpp new file mode 100644 index 00000000..ac07f57b --- /dev/null +++ b/drafts/String.hpp @@ -0,0 +1,75 @@ +/* +Because the text encoding problem on Windows, here I write some functions +related to text encoding. The utf-8 and utf-16 conversion function is provided +by win32 api. However win32 api does not provide any function about charactor +iteration or index by code point. (At least I haven't found.) I don't use icu +because it is not easy to build it on Windows and the bundled version in Windows +(https://docs.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-) +is only available after Windows 10 Creators Update. + +Luckily, both utf-8 and utf-16 encoding are easy to learn and program with if we +only do simple iteration rather than do much sophisticated work about +complicated error situations. (And I learn the internal of the encoding by the +way.) +*/ + +#pragma once +#include "WinPreConfig.hpp" + +#include "cru/common/StringUtil.hpp" + +#include <cstdint> +#include <stdexcept> +#include <string> +#include <string_view> + +namespace cru::platform::win { +std::string ToUtf8String(const std::wstring_view& string); +std::wstring ToUtf16String(const std::string_view& string); + +inline bool IsSurrogatePair(wchar_t c) { return c >= 0xD800 && c <= 0xDFFF; } + +inline bool IsSurrogatePairLeading(wchar_t c) { + return c >= 0xD800 && c <= 0xDBFF; +} + +inline bool IsSurrogatePairTrailing(wchar_t c) { + return c >= 0xDC00 && c <= 0xDFFF; +} + +class Utf16Iterator : public Object { + static_assert( + sizeof(wchar_t) == 2, + "Emmm, according to my knowledge, wchar_t should be 2-length on " + "Windows. If not, Utf16 will be broken."); + + public: + Utf16Iterator(const std::wstring_view& string) : string_(string) {} + + CRU_DEFAULT_COPY(Utf16Iterator) + CRU_DEFAULT_MOVE(Utf16Iterator) + + ~Utf16Iterator() = default; + + public: + void SetToHead() { position_ = 0; } + + // Advance current position and get next code point. Return k_code_point_end + // if there is no next code unit(point). Throw TextEncodeException if decoding + // fails. + CodePoint Next(); + + Index CurrentPosition() const { return this->position_; } + + private: + std::wstring_view string_; + Index position_ = 0; +}; + +Index IndexUtf8ToUtf16(const std::string_view& utf8_string, Index utf8_index, + const std::wstring_view& utf16_string); + +Index IndexUtf16ToUtf8(const std::wstring_view& utf16_string, Index utf16_index, + const std::string_view& utf8_string); + +} // namespace cru::platform::win |