LINUX.ORG.RU

Слетает выравнивание для строк с русскими символами. Unicode в Си.

 , , , ,


1

3

Всех приветствую!

Все таки решил создать новую тему.

Хотя обсуждение началось в этой теме, но там оно больше уклонилось в лингвистическую строну, да и у меня немного другой вопрос.

Проблема вот в чем: если в printf-е мы ставим «%30s», то у нас выводится сначало 30-length(str) пробелов, потом наша строка str длиной length(str).

Но это не работает, если в строке есть не англ. символы.

Чтобы проверить какое будет поведение под Windows и Linux, а также с char* и wchar_t*, набросал программку:



#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <locale.h>
#include <wchar.h>
#include <uchar.h> // for char16_t


char *str_dup(char const *in)
{
    size_t len = strlen(in);
    char *out = malloc(len+1);
    strncpy(out, in, len+1);
    return out;
}

wchar_t *wstr_dup(wchar_t const *in)
{
    size_t len = wcslen(in);
    wchar_t *out = malloc((len+1)* sizeof(wchar_t));
    wcsncpy(out, in, (len+1) );
    return out;
}

wchar_t* convert_to_wstr(const char* cstr)
{
    mbstate_t state;
    memset(&state, 0, sizeof(state) );
    size_t out_sz = 1 + mbsrtowcs(NULL, &cstr, 0, &state);
    
    wchar_t* ws = malloc(out_sz*sizeof(wchar_t) );
    mbstowcs(ws, cstr, out_sz);
    return ws;
}

char* convert_to_cstr(const wchar_t* wstr)
{
    mbstate_t state;
    memset(&state, 0, sizeof(state) );
    size_t out_sz = 1 + wcsrtombs(NULL, &wstr, 0, &state);
    
    char* cs = malloc(out_sz*sizeof(char) );
    wcstombs(cs, wstr, out_sz);
    return cs;
}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
typedef struct CSTRUCT
{
	char* data_only_engl; // только англ. буквы
        char* data_from_file; // строка из файла
        char* data_from_prog; // строка в программе
} CSTRUCT;

CSTRUCT* cs_create(const char* cstr)
{
	CSTRUCT* cs = malloc(sizeof(CSTRUCT));

	cs->data_only_engl = str_dup("Hello Friend");
	cs->data_from_prog = str_dup(cstr);

	FILE *input = fopen("text.txt", "r");
    char buff[128];
    memset(buff, '\0', 128);
    fgets(buff, 128, input);
    	cs->data_from_file = str_dup(buff);
	return cs;
}

void cs_print(CSTRUCT* cs)
{
	fprintf(stdout,"------------------------------|\n");
	const char FMT[] = "%30s| %2ld\n";
	fprintf(stdout, FMT, cs->data_only_engl, strlen(cs->data_only_engl) );
	fprintf(stdout, FMT, cs->data_from_prog, strlen(cs->data_from_prog) );
	fprintf(stdout, FMT, cs->data_from_file, strlen(cs->data_from_file) );
	fprintf(stdout,"123456789012345678901234567890|\n"); // ровно 30 символов
}

void cs_free(CSTRUCT** cs)
{
	free( (*cs)->data_from_prog);
	free( (*cs)->data_from_file);
	free( (*cs)->data_only_engl);
	free(*cs);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/



typedef struct WSTRUCT
{
	wchar_t* data_only_engl;
        wchar_t* data_from_file;
        wchar_t* data_from_prog;
} WSTRUCT;


WSTRUCT* ws_create(const char* str)
{
	WSTRUCT* ws = malloc(sizeof(WSTRUCT));
	ws->data_only_engl = wstr_dup(L"Hello Friend");
	ws->data_from_prog = convert_to_wstr(str);

	FILE *input = fopen("text.txt", "r");
    char buff[128];
    memset(buff, '\0', 128);
    fgets(buff, 128, input);
    
	ws->data_from_file = convert_to_wstr(buff);
	return ws;
}

void ws_print(WSTRUCT* ws)
{
	fprintf(stdout,"------------------------------|\n");
	const char FMT[] = "%30ls| %2ld\n";
	fprintf(stdout, FMT, ws->data_only_engl, wcslen(ws->data_only_engl) );
	fprintf(stdout, FMT, ws->data_from_prog, wcslen(ws->data_from_prog) );
	fprintf(stdout, FMT, ws->data_from_file, wcslen(ws->data_from_file) );
	fprintf(stdout,"123456789012345678901234567890|\n");
}

void ws_free(WSTRUCT** ws)
{
	free( (*ws)->data_from_prog);
	free( (*ws)->data_from_file);
	free( (*ws)->data_only_engl);
	free(*ws);
}



int main(int argc, char const *argv[])
{
	
#if defined (_WIN32)
	setlocale(LC_ALL, "ru_RU");
#else
	setlocale(LC_ALL, "ru_RU.utf8");
#endif

	printf("sizeof(char)    = %ld\n", sizeof(char) );
	printf("sizeof(char16_t)= %ld\n", sizeof(char16_t) );
	printf("sizeof(wchar_t) = %ld\n", sizeof(wchar_t) );
	printf("\n");


	CSTRUCT* cs = cs_create("Hello! Друг");
	cs_print(cs);
	cs_free(&cs);

	WSTRUCT* ws = ws_create("Hello! Друг");
	ws_print(ws);
	ws_free(&ws);


	return EXIT_SUCCESS;
}

Кратко: есть две структуры одна хранит строки в char*, другая - wchar_t*.

Обе хранят три строки: с чисто англ. символами, со строкой инициализируемой в программе и строкой считываемой из файла:

text.txt

Привет friend из файла!

В итоге под Linux получаю

sizeof(char)    = 1
sizeof(char16_t)= 2
sizeof(wchar_t) = 4

------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|
------------------------------|
                  Hello Friend| 12
               Hello! Друг| 11
Привет friend из файла!
| 24
123456789012345678901234567890|

Видно, что выравнивание сработало для строки только с англ. символами.

А хотелось бы:

------------------------------|
                  Hello Friend| 12
                   Hello! Друг| 11
       Привет friend из файла!| 23
123456789012345678901234567890|

Соответственно под Windows имею:

sizeof(char)    = 1
sizeof(char16_t)= 2
sizeof(wchar_t) = 2

------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|
------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|

Как сделать кроссплатф. вывод на экран с правильным выравниваем?



Последнее исправление: Gyros (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

Фигню написали, ему нужно выравнивает справа, а не слева. Плюс у вас всё еще фигня с тем, что strlen даёт bytes а не characters.

ТС, тебе написали еще в той теме, что в общем случае задача не решается, хоть обмажься wchar. Композитные символы и суррогатные пары ты сам не проанализируешь, потому что это еще и от шрифта зависит

#include <stdio.h>
#include <string.h>

int main(void) {
    const char *s1 = "message󠅟󠅘󠄐󠅝󠅩󠄜󠄐󠅩󠅟󠅥󠄐󠅖󠅟󠅥󠅞󠅔󠄐󠅤󠅘󠅕󠄐󠅘󠅙󠅔󠅔󠅕󠅞󠄐󠅝󠅕󠅣󠅣󠅑󠅗󠅕󠄐󠅙󠅞󠄐󠅤󠅘󠅕󠄐󠅤󠅕󠅨󠅤󠄑";
    const char *s2 = "message";

    printf("%s: %d\n", s1, (int)strlen(s1));
    printf("%s: %d\n", s2, (int)strlen(s2));
    
    return 0;
}

На разных шрифтах, рендерах, движках и консолях будет по разному выглядеть. В хромаке у меня показывает графически одинаково (как и должно быть), online compier рендерит пробелы, консоль рендерит непечатные символы.

message: 199
message: 7
PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 2)

О проблемах уже написали, но я думаю это именно то что ты ищешь:

#include <stdio.h>
#include <wchar.h>
#include <locale.h>

void right_pad(const wchar_t *name, int pad_size)
{
	wprintf(L"%*ls |\n", pad_size, name);
}

int main()
{
	setlocale(LC_ALL, "");
	right_pad(L"Привет мир!", 30);
	right_pad(L"Hello World!", 30);
	right_pad(L"Довольно длинная строка ...", 30);
	return 0;
}
                   Привет мир! |
                  Hello World! |
   Довольно длинная строка ... |

Откажись от выделения памяти, используй стандартные функции а то у тебя копия wcsdup, для файлов лучше вызывай fopen с «rb» чем «r».

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 5)
Ответ на: комментарий от LINUX-ORG-RU
char FMT[256] = {0};
sprintf(FMT,"%s%d%s\n","%",pref+strlen(str),"s");

Это жесть! Почему случайный капс? Зачем обнулять FMT? Ну и формировать через sprintf строку форматирования необязательно, вообще printf тьюринг-полный.

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

Тада как-то так

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <locale.h>

//-----------------------------------------------------------------------------
//             utf8 decoder function by Chris Wellons
//           https://github.com/skeeto/branchless-utf8
//This is free and unencumbered software released into the public domain.
void *
utf8_decode(void *buf, uint32_t *c, int *e)
{
    static const char lengths[] = {
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0
    };
    static const int masks[]  = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
    static const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
    static const int shiftc[] = {0, 18, 12, 6, 0};
    static const int shifte[] = {0, 6, 4, 2, 0};

    unsigned char *s = buf;
    int len = lengths[s[0] >> 3];

    /* Compute the pointer to the next character early so that the next
     * iteration can start working on the next character. Neither Clang
     * nor GCC figure out this reordering on their own.
     */
    unsigned char *next = s + len + !len;

    /* Assume a four-byte character and load four bytes. Unused bits are
     * shifted out.
     */
    *c  = (uint32_t)(s[0] & masks[len]) << 18;
    *c |= (uint32_t)(s[1] & 0x3f) << 12;
    *c |= (uint32_t)(s[2] & 0x3f) <<  6;
    *c |= (uint32_t)(s[3] & 0x3f) <<  0;
    *c >>= shiftc[len];

    /* Accumulate the various error conditions. */
    *e  = (*c < mins[len]) << 6; // non-canonical encoding
    *e |= ((*c >> 11) == 0x1b) << 7;  // surrogate half?
    *e |= (*c > 0x10FFFF) << 8;  // out of range?
    *e |= (s[1] & 0xc0) >> 2;
    *e |= (s[2] & 0xc0) >> 4;
    *e |= (s[3]       ) >> 6;
    *e ^= 0x2a; // top two bits of each tail byte correct?
    *e >>= shifte[len];

    return next;
}
// допишем функцию которая тупо считает сколько у нас символов, а не байт
//-----------------------------------------------------------------------------
unsigned long utf8_len(const char * string,int * error)
{
    char * next = (char*)string;
    uint32_t    code = 0;
    unsigned long len = 0;
    int err=0;
    do
    {
       next = utf8_decode(next,&code,&err);
       len++;
    }while(code != 0);
    *error = err;
    return len;
}

void myprint(const char * str)
{
    char FMT[256] = {0};
    int pref = 30;
    int err = 0;
    sprintf(FMT,"%s%d%s\n","%",pref+strlen(str)-utf8_len(str,&err),"s");
    printf(FMT,str);
}

int main(int argc, char *argv[])
{
    myprint("Hello Friend");
    myprint("Hello! Друг");
    myprint("Привет friend из файла!");
    return 0;
}

LINUX-ORG-RU ★★★★★
()

Тоже сталкивался с этим - это нужно как текстовым уи-библиотекам, так и при написании эмулятора терминала. Итог: юникодный комитет состоит из профнепригодных идиотов, и они по причине своей профнепригодности сделали всё так что без разной блоатвари нормально узнать длину юникодной строки невозможно.

И даже когда ты встроишь в свою прогу их блоатварь, всё равно останутся шансы что ты (или авторы терминал-эмулятора, которым пользуется юзер, запустивший твою прогу) что-то забыл и где-то что-то перекосится.

firkax ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

Во-первых узнай про %*s вместо костылей с двойным printf-ом, во-вторых ему надо не количество юникодных символов узнать, а количество напечатанных - это разные вещи.

И в третьих твой printf всё равно сломается, потому что конструкция %10s считает 10 байт, а не юникодные символы.

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

И даже когда ты встроишь в свою прогу их блоатварь, всё равно останутся шансы что ты (или авторы терминал-эмулятора, которым пользуется юзер, запустивший твою прогу) что-то забыл и где-то что-то перекосится.

Конечно, разные терминалы могут отобразить один символ как один или как два.

И не стоит забывать что интерпретация юникода зависит от локали, и постоянно обновляется. И поэтому же не стоит использовать ctype.h, если конечно не требуется непредсказуемый вывод который должен быть понятнее лишь человеку.

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

Из за snprintf могут быть проблемы, когда он что то обрежет, а программист не проверит. Тогда уж лучше asprintf, если он уже научился устанавливать NULL в glibc, то там хотя бы сразу вылет при непроверенной ошибке.

MOPKOBKA ★★★★★
()

Как сделать кроссплатф. вывод на экран с правильным выравниваем?

Если таблицы и чтобы не мучиться, то просто используя https://github.com/seleznevae/libfort, например.
Набросал программку™️:

#include <stdio.h>

#include "fort.h"

int main(void)
{
    ft_table_t *table = ft_create_table();

    ft_set_cell_prop(table, 0, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE, FT_ROW_HEADER);
    ft_set_cell_prop(table, FT_ANY_ROW, 1, FT_CPROP_TEXT_ALIGN, FT_ALIGNED_RIGHT);
    ft_set_cell_prop(table, FT_ANY_ROW, 2, FT_CPROP_TEXT_ALIGN, FT_ALIGNED_RIGHT);
    ft_set_cell_prop(table, FT_ANY_ROW, 3, FT_CPROP_TEXT_ALIGN, FT_ALIGNED_RIGHT);

    ft_u8write_ln(table, "N", "Водитель",  "Время",    "Ср. скорость");

    ft_u8write_ln(table, "1", "Ricciardo", "1:25.945", "222.128");
    ft_u8write_ln(table, "2", "Hamilton",  "1:26.373", "221.027");
    ft_u8write_ln(table, "3", "Слоупоков", "1:26.469", "220.782");
    ft_u8write_ln(table, "4", "周冠宇",    "х. з.",    "х. з.");

    const char *table_str = (const char *)ft_to_u8string(table);
    printf("%s\n", table_str);
    ft_destroy_table(table);

    return 0;
}
+---+-----------+----------+--------------+
| N |  Водитель |    Время | Ср. скорость |
+---+-----------+----------+--------------+
| 1 | Ricciardo | 1:25.945 |      222.128 |
| 2 |  Hamilton | 1:26.373 |      221.027 |
| 3 | Слоупоков | 1:26.469 |      220.782 |
| 4 |     周冠宇 |    х. з. |        х. з. |
+---+-----------+----------+--------------+

Но как будет в Windows, я без понятия.

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

Говно, выкидывай, не соответствует стандарту: https://i.postimg.cc/V6kP3T6D/2025-02-24-19-22-50.png

$ ./a.out 
+----+---------------------------------------------------------+
| ID |                                                    Text |
+----+---------------------------------------------------------+
| 1  |                                                    test |
| 2  |                                                 message |
| 3  |                                                 unknown |
| 4  | message󠅟󠅘󠄐󠅝󠅩󠄜󠄐󠅩󠅟󠅥 |
+----+---------------------------------------------------------+
#include <stdio.h>

#include "fort.h"

int main(void)
{
    ft_table_t *table = ft_create_table();

    ft_set_cell_prop(table, 0, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE, FT_ROW_HEADER);
    ft_set_cell_prop(table, FT_ANY_ROW, 1, FT_CPROP_TEXT_ALIGN, FT_ALIGNED_RIGHT);

    ft_u8write_ln(table, "ID", "Text");

    ft_u8write_ln(table, "1", "test");
    ft_u8write_ln(table, "2", "message");
    ft_u8write_ln(table, "3", "unknown");
    ft_u8write_ln(table, "4", "message󠅟󠅘󠄐󠅝󠅩󠄜󠄐󠅩󠅟󠅥󠄐󠅖󠅟󠅥󠅞󠅔󠄐󠅤󠅘󠅕󠄐󠅘󠅙󠅔󠅔󠅕󠅞󠄐󠅝󠅕󠅣󠅣󠅑󠅗󠅕󠄐󠅙󠅞󠄐󠅤󠅘󠅕󠄐󠅤󠅕󠅨󠅤󠄑");

    const char *table_str = (const char *)ft_to_u8string(table);
    printf("%s\n", table_str);
    ft_destroy_table(table);

    return 0;
}
PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от firkax

конструкция %10s считает 10 байт, а не юникодные символы.

Это потому что вот это?

An optional minimum field width. If the converted value has fewer bytes than the field width, it shall be padded with characters by default on the left;

https://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html

Но почему wcslen на MS Windows для ‘Hello! Друг’ выдает 15 вместо 11 при двухбайтном wchar_t?

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

C wprintf у меня вообще ничего не выводит на экран, в Linux-е

sizeof(char)    = 1
sizeof(char16_t)= 2
sizeof(wchar_t) = 4

------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|

C wprintf под Windows 11, что-то выводит печальное

sizeof(char)    = 1
sizeof(char16_t)= 2
sizeof(wchar_t) = 2

------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|
------------------------------|
                             H| 12
                             H| 15
                              | 37
123456789012345678901234567890|

PS. Естественно, ставлю

#if defined (_WIN32)
	setlocale(LC_ALL, "ru_RU");
	//printf("Windows\n");
#else
	setlocale(LC_ALL, "ru_RU.utf8");
	//printf("Linux\n");
#endif
Gyros
() автор топика
Ответ на: комментарий от MirandaUser2

Это потому что вот это?

Ну, и поэтому наверно тоже, но у меня объяснение проще: нативная кодировка для printf - однобайтовая, и он везде оперирует именно байтами, кроме, может быть, каких-то поздних расширений, где явно в документации указано что символы другие.

Но почему wcslen на MS Windows для ‘Hello! Друг’ выдает 15 вместо 11 при двухбайтном wchar_t?

Наверно стоит на этот счёт отослать багрепорт в мс (если это правда так, я не проверял).

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

Может потому что я компилирую в Linux

x86_64-w64-mingw32.static-gcc -std=c11 deft.c  -o deft.exe -static-libgcc -L/home/bark/mxe/usr/x86_64-w64-mingw32.static/lib  -lwinmm -lgdi32

а запускаю exe-шник в Винде?

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

Откажись от выделения памяти, используй стандартные функции а то у тебя копия wcsdup

Ну и что, я все равно потом в «деструкторе» ws_free все по-free-каю.

А какие станд. ф-ции?

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

но только вы литералы выводите, уже заранее зашитые строки

А какая разница между обычными строками? Наверное ты хотел const char * выводить? Можешь их сконвертировать в wchar_t, или через GNU libunistring вычислить размер строки без конвертации вот так:

#include <stdio.h>
#include <locale.h>
#include <string.h>
#include <unistr.h>

void right_pad(const char *text, int pad_size)
{
	int mblen = u8_mbsnlen((const uint8_t*)text, strlen(text));
	printf("%*s%s |\n", pad_size - mblen, "", text);
}

int main()
{
	setlocale(LC_ALL, ".UTF8");
	right_pad("Привет мир!", 30);
	right_pad("Hello World!", 30);
	right_pad("Довольно длинная строка ...", 30);
	return 0;
}
MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от PPP328

Скопируйте строки из моего сообщения и выведите через go. Удивитесь

там надо через go-runewidth считать, тогда будет ок

package main

import (
	"fmt"
	wid "github.com/mattn/go-runewidth"
)

func main() {

	s1 := "message󠅟󠅘󠄐󠅝󠅩󠄜󠄐󠅩󠅟󠅥󠄐󠅖󠅟󠅥󠅞󠅔󠄐󠅤󠅘󠅕󠄐󠅘󠅙󠅔󠅔󠅕󠅞󠄐󠅝󠅕󠅣󠅣󠅑󠅗󠅕󠄐󠅙󠅞󠄐󠅤󠅘󠅕󠄐󠅤󠅕󠅨󠅤󠄑"
	s2 := "message"

	fmt.Printf("%s: %d %d\n", s1, len(s1), wid.StringWidth(s1))
	fmt.Printf("%s: %d %d\n", s2, len(s2), wid.StringWidth(s2))

}

результат

message󠅟󠅘󠄐󠅝󠅩󠄜󠄐󠅩󠅟󠅥: 199 7
message: 7 7
anonymous
()
Ответ на: комментарий от anonymous

там надо через go-runewidth считать

Что, собственно, подтверждает слова выше - «нужен какой-то левый блоб, который хренпоймикак работает». На С никто точно также не мешает его использовать, Go тут никак не участвует.

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

Вот рез-ты первонач вашей программы:

Linux

./mork
                   Привет мир! |
                  Hello World! |
   Довольно длинная строка ... |

wine - Windows

wine mork.exe
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
                    |
                  Hello World! |
    |

Windows

                   Привет мир! |
                  Hello World! |
   Довольно длинная строка ... |

Сработало. Непонятно почему у меня не выводится. Хотя я ничего сверхестественного не делаю со строками

Может дело в

setlocale(LC_ALL, "");

именно пустую строку давать ей.

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

UTF-8 в консоли недавнее нововведение, подозреваю что в Wine и более старых Windows не реализованное. wchar_t надежнее, у тебя уже есть готовый код для конвертации. Он должен без проблем работать.

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

Я поставил в вашей первоначальной

setlocale(LC_ALL, "ru_RU");

и она «сломалась».

И наоборот, в своей поставил

setlocale(LC_ALL, "");

но … (несмотря на все надежды) она не выдала правильный вывод.

Вот загадка-то. То ли действительно что-то со строками делается, то ли ?

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

Ну работает же нормально в Windows 11 и Linux. Только в Wine и старых Windows будут проблемы, но их можно избежать сконвертировав весь вывод в wchar_t. В Linux конвертацию можно оставить из std.

#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#include <windows.h>
#endif

#ifdef _WIN32
wchar_t* cstr2wstr(const char* s)
{
  size_t s_len, ws_len;
  wchar_t *ws;

  s_len = strlen(s);
  ws_len = MultiByteToWideChar(CP_UTF8, 0, s, s_len, NULL, 0);
  if ((ws = malloc(ws_len + 1)) == NULL) {
    wprintf(L"cstr2wstr: low memory\n");
    exit(1);
  }
  MultiByteToWideChar(CP_UTF8, 0, s, s_len, ws, ws_len);
  ws[ws_len] = 0;
  return ws;
}
#endif

void right_pad(const char *text, int pad_size)
{
	wchar_t *wtext = cstr2wstr(text);
	wprintf(L"%*ls |\n", pad_size, wtext);
	free(wtext);
}

int main()
{
	setlocale(LC_ALL, "");
	right_pad("Привет мир!", 30);
	right_pad("Hello World!", 30);
	right_pad("Довольно длинная строка ...", 30);
	return 0;
}

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

Вывело у меня в Windows с wprintf, надо было поправить форматную строку

const wchar_t FMT[] = L"%30ls| %2ld\n";

l забыл маленькую поставить перед s.

Но все равно какие-то отступы:

------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|
------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|

и даже перенос на новую строку..

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

mbsrtowcs и mbstowcs не cправятся под Windows?

Судя по тому что я читал, в зависимости от сложных условий, оно либо работает, либо нет. А MultiByteToWideChar это более рабочий способ.

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

Для русского и английского, и других распространенных языков можно добиться, потому что терминалы их отображают одинаково. Проблемы возникают с Emoji и наверное всякой арабской вязью.

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

Выводы:

  1. Перед выводом на (экран) консоль лучше переводить в wchar_t* и wprintf(L"%ls \n", wstr);

  2. Ровного форматирования (выравнивания) при выводе на экран добиться НЕЛЬЗЯ. (хотя прям не верится)

  3. При выводе в файл сохранять как char* (тогда будет utf8 текстовый файл).

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

1. Да, это наверное самый простой вариант.

2. Для всего юникода так и есть, а вот для русского, английского и других распространенных языков можно.

3. Да, и лучше использовать режим «rb» вместо «r», и «wb» вместо «w». Иначе Windows будет искажать переводы строк.

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

Ты мой последний пример запускаешь, или про свой спрашиваешь?

В твоем самом первом примере, у тебя неправильно идет конвертация, а потом неправильно идет вывод.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)

никак. Вот прилетит тебе utf-8 c арабской вязью. Сколько это символов?
Ты можешь распарсить utf-8 и посчитать, сколько там кодпоинтов, но кодпоинт != сивмол.
Чтобы что-то выровнять таким образом - надо смотреть в конкретную реализацию терминала и написать расчёт терминального отсутпа в пробелах под неё

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

притом кода там немного

Значит реализация очень ограничена и не будет работать корректно например на zalgo, rtl, смайликах или арабской вязи.

PPP328 ★★★★★
()
Последнее исправление: PPP328 (всего исправлений: 1)