diff options
Diffstat (limited to 'Source/cm_codecvt.cxx')
-rw-r--r-- | Source/cm_codecvt.cxx | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/Source/cm_codecvt.cxx b/Source/cm_codecvt.cxx new file mode 100644 index 0000000..869dd32 --- /dev/null +++ b/Source/cm_codecvt.cxx @@ -0,0 +1,215 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cm_codecvt.hxx" +#include <limits> + +#if defined(_WIN32) +#include <windows.h> +#undef max +#include <cmsys/Encoding.hxx> +#endif + +codecvt::codecvt(Encoding e) + : m_lastState(0) +#if defined(_WIN32) + , m_codepage(0) +#endif +{ + switch (e) { + case codecvt::ANSI: +#if defined(_WIN32) + m_noconv = false; + m_codepage = CP_ACP; + break; +#endif + // We don't know which ANSI encoding to use for other platforms than + // Windows so we don't do any conversion there + case codecvt::UTF8: + // Assume internal encoding is UTF-8 + case codecvt::None: + // No encoding + default: + m_noconv = true; + } +} + +codecvt::~codecvt(){}; + +bool codecvt::do_always_noconv() const throw() +{ + return m_noconv; +}; + +std::codecvt_base::result codecvt::do_out(mbstate_t& state, const char* from, + const char* from_end, + const char*& from_next, char* to, + char* to_end, char*& to_next) const +{ + if (m_noconv) { + return noconv; + } + std::codecvt_base::result res = error; +#if defined(_WIN32) + from_next = from; + to_next = to; + bool convert = true; + size_t count = from_end - from; + const char* data = from; + unsigned int& stateId = reinterpret_cast<unsigned int&>(state); + if (count == 0) { + return codecvt::ok; + } else if (count == 1) { + if (stateId == 0) { + // decode first byte for UTF-8 + if ((*from & 0xF8) == 0xF0 || // 1111 0xxx; 4 bytes for codepoint + (*from & 0xF0) == 0xE0 || // 1110 xxxx; 3 bytes for codepoint + (*from & 0xE0) == 0xC0) // 110x xxxx; 2 bytes for codepoint + { + stateId = findStateId(); + codecvt::State& s = m_states.at(stateId - 1); + s.bytes[0] = *from; + convert = false; + if ((*from & 0xF8) == 0xF0) { + s.totalBytes = 4; + } else if ((*from & 0xF0) == 0xE0) { + s.totalBytes = 3; + } else if ((*from & 0xE0) == 0xC0) { + s.totalBytes = 2; + } + s.bytesLeft = s.totalBytes - 1; + }; + // else 1 byte for codepoint + } else { + codecvt::State& s = m_states.at(stateId - 1); + s.bytes[s.totalBytes - s.bytesLeft] = *from; + s.bytesLeft--; + data = s.bytes; + count = s.totalBytes - s.bytesLeft; + if ((*from & 0xC0) == 0x80) { // 10xx xxxx + convert = s.bytesLeft == 0; + } else { + // invalid multi-byte + convert = true; + } + if (convert) { + s.used = false; + if (stateId == m_lastState) { + m_lastState--; + } + stateId = 0; + } + } + if (convert) { + std::wstring wide = cmsys::Encoding::ToWide(std::string(data, count)); + int r = WideCharToMultiByte(m_codepage, 0, wide.c_str(), + static_cast<int>(wide.size()), to, + to_end - to, NULL, NULL); + if (r > 0) { + from_next = from_end; + to_next = to + r; + res = ok; + } + } else { + res = partial; + from_next = from_end; + to_next = to; + } + } +#else + static_cast<void>(state); + static_cast<void>(from); + static_cast<void>(from_end); + static_cast<void>(from_next); + static_cast<void>(to); + static_cast<void>(to_end); + static_cast<void>(to_next); + res = codecvt::noconv; +#endif + return res; +}; + +std::codecvt_base::result codecvt::do_unshift(mbstate_t& state, char* to, + char* to_end, + char*& to_next) const +{ + std::codecvt_base::result res = error; + to_next = to; +#if defined(_WIN32) + unsigned int& stateId = reinterpret_cast<unsigned int&>(state); + if (stateId > 0) { + codecvt::State& s = m_states.at(stateId - 1); + s.used = false; + if (stateId == m_lastState) { + m_lastState--; + } + stateId = 0; + std::wstring wide = cmsys::Encoding::ToWide( + std::string(s.bytes, s.totalBytes - s.bytesLeft)); + int r = WideCharToMultiByte(m_codepage, 0, wide.c_str(), + static_cast<int>(wide.size()), to, to_end - to, + NULL, NULL); + if (r > 0) { + to_next = to + r; + res = ok; + } + } else { + res = ok; + } +#else + static_cast<void>(state); + static_cast<void>(to_end); + res = ok; +#endif + return res; +}; + +int codecvt::do_max_length() const throw() +{ + return 4; +}; + +int codecvt::do_encoding() const throw() +{ + return 0; +}; + +unsigned int codecvt::findStateId() const +{ + unsigned int stateId = 0; + bool add = false; + const unsigned int maxSize = std::numeric_limits<unsigned int>::max(); + if (m_lastState >= maxSize) { + m_lastState = 0; + } + if (m_states.size() <= m_lastState) { + add = true; + } else { + unsigned int i = m_lastState; + while (i < maxSize) { + codecvt::State& s = m_states.at(i); + i++; + if (!s.used) { + m_lastState = i; + stateId = m_lastState; + s.used = true; + s.totalBytes = 0; + s.bytesLeft = 0; + break; + } + if (i >= m_states.size()) { + i = 0; + } + if (i == m_lastState) { + add = true; + break; + } + } + }; + if (add) { + codecvt::State s = { true, 0, 0, { 0, 0, 0, 0 } }; + m_states.push_back(s); + m_lastState = (unsigned int)m_states.size(); + stateId = m_lastState; + } + return stateId; +}; |