MSVC хранит wchar_t
в wstring
с. Их можно интерпретировать как 16-битные слова в кодировке Unicode или что-то еще.
Если вы хотите получить доступ к символам или символам Юникода, вам придется обрабатывать указанную необработанную строку по стандарту Юникода. Вы, вероятно, также хотите обрабатывать обычные угловые дела без поломок.
Вот эскиз такой библиотеки. Это примерно вдвое эффективнее, чем память, но дает вам доступ к глифам Юникода на месте в std::string
. Он опирается на приличный класс array_view
, но вы все равно хотите написать один из них:
struct unicode_char : array_view<wchar_t const> {
using array_view<wchar_t const>::array_view<wchar_t const>;
uint32_t value() const {
if (size()==1)
return front();
Assert(size()==2);
if (size()==2)
{
wchar_t high = front()-0xD800;
wchar_T low = back()-0xDC00;
return (uint32_t(high)<<10) + uint32_t(low);
}
return 0; // error
}
static bool is_high_surrogate( wchar_t c ) {
return (c >= 0xD800 && c <= 0xDBFF);
}
static bool is_low_surrogate( wchar_t c ) {
return (c >= 0xDC00 && c <= 0xDFFF);
}
static unicode_char extract( array_view<wchar_t const> raw )
{
if (raw.empty())
return {};
if (raw.size()==1)
return raw;
if (is_high_surrogate(raw.front()) && is_low_surrogate(*std::next(raw.begin())))
return {raw.begin(), raw.begin()+2);
return {raw.begin(), std::next(raw.begin())};
}
};
static std::vector<unicode_char> as_unicode_chars( array_view<wchar_t> raw )
{
std::vector<unicode_char> retval;
retval.reserve( raw.size() ); // usually 1:1
while(!raw.empty())
{
retval.push_back( unicode_char::extract(raw) );
Assert( retval.back().size() <= raw.size() );
raw = {raw.begin() + retval.back().size(), raw.end()};
}
return retval;
}
struct unicode_glyph {
std::array< unicode_char, 3 > buff;
std::size_t count=0;
unicode_char const* begin() const {
return buff.begin();
}
unicode_char const* end() const {
return buff.begin()+count;
}
std::size_t size() const { return count; }
bool empty() { return size()==0; }
unicode_char const& front() const { return *begin(); }
unicode_char const& back() const { return *std::prev(end()); }
array_view< unicode_char const > chars() const { return {begin(), end()}; }
array_view< wchar_t const > wchars() const {
if (empty()) return {};
return { front().begin(), back().end() };
}
void append( unicode_char next ) {
Assert(count<3);
buff[count++] = next;
}
unicode_glyph() {}
static bool is_diacrit(unicode_char c) const {
auto v = c.value();
return is_diacrit(v);
}
static bool is_diacrit(uint32_t v) const {
return
((v >= 0x0300) && (v <= 0x0360))
|| ((v >= 0x1AB0) && (v <= 0x1AFF))
|| ((v >= 0x1DC0) && (v <= 0x1DFF))
|| ((v >= 0x20D0) && (v <= 0x20FF))
|| ((v >= 0xFE20) && (v <= 0xFE2F));
}
static size_t diacrit_count(unicode_char c) const {
auto v = c.value();
if (is_diacrit(v))
return 1 + ((v >= 0x035C)&&(v<=0x0362));
else
return 0;
}
static unicode_glyph extract( array_view<const unicode_char> raw ) {
unicode_glyph retval;
if (raw.empty())
return retval;
if (raw.size()==1)
{
retval.append(raw.front());
return retval;
}
retval.count = diacrit_count( *std::next(raw.begin()) )+1;
std::copy( raw.begin(), raw.begin()+retval.count, retval.buff.begin() );
return retval;
}
};
static std::vector<unicode_glyph> as_unicode_glyphs( array_view<unicode_char> raw )
{
std::vector<unicode_glyph> retval;
retval.reserve( raw.size() ); // usually 1:1
while(!raw.empty())
{
retval.push_back( unicode_glyph::extract(raw) );
Assert( retval.back().size() <= raw.size() );
raw = {raw.begin() + retval.back().size(), raw.end()};
}
return retval;
}
static std::vector<unicode_glyph> as_unicode_glyphs( array_view<wchar_t> raw )
{
return as_unicode_glyphs( as_unicode_chars( raw ) );
}
более умный бит кода будет генерировать unicode_char
s и unicode_glyph
s на лету с фабричным итератором некоторого вида. Более компактная реализация отслеживала бы тот факт, что указатель конца предыдущего и указатель начала следующего всегда идентичны и объединяли их вместе. Другая оптимизация заключалась бы в использовании оптимизации небольших объектов на глифе, основанной на предположении, что большинство глифов - это один символ, и использовании динамического выделения, если их два.
Обратите внимание, что я рассматриваю CGJ как стандартный диакрит, а двойные диакриты - как набор из 3 символов, которые образуют один (юникод), но полудиакриты не объединяют вещи в один глиф. Это все сомнительные варианты.
Это было написано в приступе бессонницы. Надеюсь, это хоть как-нибудь сработает.