LINUX.ORG.RU

Вывод в C++ в консоль символа Unicode, исходя из его code point

 , ,


1

2

Как правильно переписать данный код:

int64_t Spades = 0x1f0a0; // U+1f0a0
for(int64_t i = Spades; i < Spades + 0xf; ++i)
    std::cout << (char*)&i << std::endl; // Не работает!
Я понимаю, что в C++ hex-значение символа в UTF-8 не будет совпадать с Unicode, но как сделать правильно - не могу разобраться.

Я хочу напечатать первую строчку из этой таблицы, оперируя hex-значениями Unicode: http://www.fileformat.info/info/unicode/block/playing_cards/utf8test.htm

Вот так работает:

std::cout << u8"\U0001f0a1" << std::endl;
Как сделать так же в runtime, учитывая, что codepoint - int?

Chaser_Andrey ★★★★★
() автор топика

Вот рабочий код:

std::string ss = u8"\U0001f0a0";
for (int i = 0; i < 0xE; ++i) {
    ss[ss.length() - 1] += 1;
    std::cout << ss << std::endl;
}
Но его недостаток в том, что мы оперируем строкой, которая создана из литерала. Как создать строку из int? Банальный memcpy не работает.

Chaser_Andrey ★★★★★
() автор топика
Ответ на: комментарий от Chaser_Andrey

Либо взять какую-то библиотеку по конвертации из UTF32 в UTF-8, либо разобраться с форматом UTF-8, он простой. В двух словах: если старший бит байта в строке 0, то имеем обычный ASCII символ. Иначе количество ведущих 1 показывает число байт в кодированном представлении кодовой точки (не меньше двух единиц). Все последующие байты начинаются с 10.

Пример: 0x1f0a0 = 00011111000010100000b, если не путаю. Разбиваем по 6 бит: 011111 000010 100000, итого 3. В 3 байта не влезет: надо 1110xxxx, а тогда xxxx < 11111, поэтому придется использовать 4 байта (на самом деле на википедии фиксированные диапазоны указаны, но думаю, что они по такой же логике разбиты). В 4 байта будет так: 11110000 10011111 10000010 10100000.

Kiborg ★★★
()
Ответ на: комментарий от Chaser_Andrey

Заморачиваться со строками здесь не нужно. Можно просто выводить wchar_t в std::wcout.

std::locale::global(std::locale("en_US.UTF-8"));
uint32_t Spades = 0x1f0a0; // U+1f0a0
for(uint32_t i = Spades; i < Spades + 0xf; ++i)
	std::wcout << (wchar_t)i << std::endl;

Правда под оффтопиком работать не будет, там wchar_t 16-битный и с UTF-8 все плохо.

archie
()
#include <iostream>
#include <wchar.h>
#include <locale.h>
#include <string.h>

void printwc(wchar_t wc)
{
	char buf[2 * MB_CUR_MAX];

	mbstate_t state;
	memset(&state,0,sizeof(state));

	size_t offset1 = wcrtomb(buf, wc, &state);

	if(offset1 == size_t(-1))
	{
		std::cerr << "wcrtomb failed (1)" << std::endl;
		return;
	}

	size_t offset2 = wcrtomb(buf + offset1, L'\0', &state);

	if(offset2 == size_t(-1))
	{
		std::cerr << "wcrtomb failed (2)" << std::endl;
		return;
	}

	std::cout << buf << std::endl;
}

int main()
{
	setlocale(LC_ALL, "");

	printwc(0x1f0a1);

	return 0;
}
Manhunt ★★★★★
()
Ответ на: комментарий от korvin_

Разбиваем по 6 бит

Наркоман, что ли?

out.append(1, static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07)));
out.append(1, static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f)));
out.append(1, static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f)));
out.append(1, static_cast<char>(0x80 | (codepoint & 0x3f)));

Числа 6, 12, 18 видишь?

Kiborg ★★★
()
Ответ на: комментарий от Manhunt

gcc 5.2, УМВР. Возможно в 4.8 не допилено конвертирование UTF-32 -> UTF-8 в стандартной С++ библиотеке. Или в системе нет локали en_US.UTF-8.

archie
()

Собрал gcc 5.2, с ним работает вот такой код


char32_t u = 0x1F0A0;
    
for(uint32_t i = u; i < u + 0xF; ++i) {
    std::u32string source({i});
    std::wstring_convert<std::codecvt_utf8<char32_t>,char32_t> convert;
    std::string dest = convert.to_bytes(source);  
    std::cout << dest;
}

Chaser_Andrey ★★★★★
() автор топика
Ответ на: комментарий от Kiborg

Да, в свежих gcc есть конвертация изкоробки.

Chaser_Andrey ★★★★★
() автор топика
Ответ на: комментарий от archie

Разве не надо избавляться от wchar_t в пользу char16_t и char32_t? Ведь wchar_t бывает непредсказуем на разных платформах и компиляторах.

Chaser_Andrey ★★★★★
() автор топика
Ответ на: комментарий от Chaser_Andrey

Впрочем, мне кажется, что это не эффективный метод.

Chaser_Andrey ★★★★★
() автор топика
Ответ на: комментарий от x905

Неплохо. Учту в Qt-проектах. Но сейчас нужно решение без Qt. Выше я написал найденное решение, только я сомневаюсь в его быстродействии.

Chaser_Andrey ★★★★★
() автор топика
Последнее исправление: Chaser_Andrey (всего исправлений: 1)
Ответ на: комментарий от Chaser_Andrey

Разве не надо избавляться от wchar_t в пользу char16_t и char32_t?

Нужность char16_t и char32_t примерно такая же, как и у других типов фиксированной разрядности - для описания бинарных форматов, протоколов передачи данных и в прочих случаях, когда важна точная разрядность. Если же нужны просто какие-то юникодные символы, неважно какой разрядности, то wchar_t - вполне себе ок.

Конкретно в том примере я использовал wchar_t тупо потому, что std::wcout не умеет правильно форматировать char16_t/char32_t, а аналогов std::wcout для char16_t/char32_t в С++ не завезли.

archie
()
Ответ на: комментарий от Chaser_Andrey

Бррр... так хардкодить локаль - плохо.

Ну можно (и даже нужно) использовать дефолтную локаль вместо хардкода:

std::locale::global(std::locale(""));
Или если не нравится глобальная установка локали, то можно задать ее только для std::wcout:
std::ios_base::sync_with_stdio(false);
std::wcout.imbue(std::locale(""));
Пойнт в том, что потоки ввода/вывода сами умеют в конвертацию из wchar_t, да и код с ними читается попроще. Ну можно конечно и руками строки туда-сюда гонять.

archie
()
Ответ на: комментарий от archie

std::wcout

Не в коем случае. UB при наличии printf, std::cout в остальном коде.

А так всё верно. Инициализируем локаль, и используем всё то, где есть преобразования между Юникодами

#include <uchar.h>
#include <iostream>
#include <cstdlib>
#include <clocale>

int main() {
  std::setlocale(LC_ALL, "");

  char32_t cards[] = {
    0x1f0a0, 0x1f0a1, 0x1f0a2, 0x1f0a3, 0x1f0a4, 0x1f0a5, 0x1f0a6, 0x1f0a7,
    0x1f0a8, 0x1f0a9, 0x1f0aa, 0x1f0ab, 0x1f0ac, 0x1f0ad, 0x1f0ae, 0x1f0af
  };
  int cards_size = 16;

  for(int i=0; i<cards_size; ++i) {
    std::mbstate_t state{};
    char out[MB_CUR_MAX+1];
    int rc = c32rtomb(&out[0], cards[i], &state);
    out[rc] = '\0';
    std::cout << out;
  }
  std::cout << std::endl;

  return 0;
}
AlexVR ★★★★★
()
Ответ на: комментарий от AlexVR

используем всё то, где есть преобразования между Юникодами

Разве c32rtomb - это преобразование между юникодами, а не преобразование между юникодом и текущей локалью?

int rc = c32rtomb

Разве возвращается int, а не size_t?

Возвращаемое значение на -1 проверять не нужно?

out[rc] = '\0';

Не может ли в некоторых локалях получиться так, что часть байтов останется в state до следующего вызова c32rtomb(), а не попадет сразу в out?

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 2)
Ответ на: комментарий от Manhunt

Возвращаемое значение на -1 проверять не нужно?

Стоит

Не может ли в некоторых локалях получиться так, что часть байтов останется в state до следующего вызова c32rtomb(), а не попадет сразу в out?

Точно, стоило использовать MB_LEN_MAX вместо MB_CUR_MAX.

AlexVR ★★★★★
()
Ответ на: комментарий от archie

Можно и в char пихать UTF8, правда на букву будет то 1, то 2 (а если нужны какие-то другие языки и спецсимволы, кроме русского и английского, то и 3, и 4, а в далёком будущем могут и 5, и 6 стать) char-а использоваться.

peregrine ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.