Я пытаюсь написать функцию, которая будет запускать алгоритм фрибиди на std::string
и возвращать переупорядоченный std::string
. Я надеюсь, что он будет достаточно безопасным для любого std::string
, и в случае, если что-то пойдет не так, он может вернуть исходный std::string
.
Я видел много примеров в Интернете, в которых используется std::wstring
, но мне интересно, смогу ли я избежать этого преобразования. Вот моя попытка (возможно, я забыл некоторые включения).
# fribidi-test.cpp
#include <cstring>
#include <iostream>
#include <string>
#include <stdio.h>
#define FRIBIDI_NO_DEPRECATED
#include <fribidi/fribidi.h>
std::string fribidi_str_convert(std::string string_orig) {
std::cerr << "dbg: orig: " + string_orig + "\n";
FriBidiChar fribidi_in_char;
FriBidiStrIndex fribidi_len = fribidi_charset_to_unicode(
FRIBIDI_CHAR_SET_UTF8,
string_orig.c_str(),
string_orig.size(),
&fribidi_in_char
);
fprintf(stderr, "len is %i\n", fribidi_len);
// https://github.com/fribidi/fribidi#api
// Let fribidi think about the main direction by it's own (https://stackoverflow.com/q/58166995/4935114)
FriBidiCharType fribidi_pbase_dir = FRIBIDI_TYPE_LTR;
// Prepare output variable
FriBidiChar fribidi_visual_char;
fribidi_boolean stat = fribidi_log2vis(
/* input */
&fribidi_in_char,
fribidi_len,
&fribidi_pbase_dir,
/* output */
&fribidi_visual_char,
NULL,
NULL,
NULL
);
fprintf(stderr, "stat is: %d\n", stat);
if (stat) {
char string_formatted_ptr;
// Convert from fribidi unicode back to ptr
FriBidiStrIndex new_len = fribidi_unicode_to_charset(
FRIBIDI_CHAR_SET_UTF8,
&fribidi_visual_char,
fribidi_len,
&string_formatted_ptr
);
fprintf(stderr, "new_len is: %d\n", new_len);
if (new_len) {
fprintf(stderr, "string_formatted_ptr is: %s\n", &string_formatted_ptr);
std::string string_formatted_out(&string_formatted_ptr, new_len);
return string_formatted_out;
};
};
return string_orig;
};
int main() {
std::string orig = "אריק איינשטיין";
std::cerr << "main: orig: " + orig + "\n";
std::cerr << "main: transformed: " + fribidi_str_convert(orig) + "\n";
};
Я компилирую и запускаю его с помощью:
g++ $(pkg-config --libs fribidi) fribidi-test.cpp -o fribidi-test && ./fribidi-test
Моя проблема в том, что я получаю искаженный вывод:
main: orig: ןייטשנייא קירא
dbg: orig: ןייטשנייא קירא
len is 14
stat is: 2
new_len is: 27
string_formatted_ptr is: אĐןייטשנייא קי
main: transformed: אĐןייטשנייא ק
Этого персонажа Đ
там быть не должно. Что я хочу получить:
main: orig: ןייטשנייא קירא
dbg: orig: ןייטשנייא קירא
len is 14
stat is: 2
new_len is: 27
string_formatted_ptr is: אריק איינשטיין
main: transformed: אריק איינשטיין
Это связано с кодировкой UTF16? а то что новая длина 27 - почти в два раза больше исходной длины?
@KamilCuk Я добавил ссылку на примеры, которые видел в Интернете. Я не уверен, что вы имеете в виду под этой опечаткой };
vs }
- этот код является точной копией того, что было успешно скомпилировано для меня. У меня были другие ошибки, когда я пытался использовать char *string_formatter_ptr;
...
Вы можете сделать };;;;;;;;;;;;;;
, если хотите, ;
просто бесполезен. Просто }
. В вашей программе есть };
.
Это очень неправильно. Вы не можете ожидать, что сохраните строку в один символ.
Чар есть чар. Это не указатель. Не строка. Не забудьте скомпилировать свои программы с помощью -fsanitize=undefined
, а также проверить с помощью valgrind.
FriBidiChar fribidi_in_char;
FriBidiStrIndex fribidi_len = fribidi_charset_to_unicode(....
&fribidi_in_char
);
char string_formatted_ptr;
FriBidiStrIndex new_len = fribidi_unicode_to_charset(...
&string_formatted_ptr
);
Кроме того, };
— просто используйте }
. Нет (необходимости) ;
после }
(в этих случаях).
Это cstdio
в C++.
Предпочитаю << string << string
вместо << string + string
, чтобы (я думаю) уменьшить выделение памяти.
Fribidi API плохой, потому что я не вижу, как рассчитать память, необходимую для charset_to_unicode
. Даже программа fribidy
— https://github.com/fribidi/fribidi/blob/cffa3047a0db9f4cd391d68bf98ce7b7425be245/bin/fribidi-main.c#L64 — просто использует постоянное количество super big value. Кроме того, программа fribidi — это пример, в котором не используется std::wstring, поскольку она написана на C.
Следующая программа использует постоянный большой размер буфера, как и программа fribidi:
#include <cassert>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <iomanip>
#define FRIBIDI_NO_DEPRECATED
#include <fribidi/fribidi.h>
#define MAX_STR_LEN 65000
std::string fribidi_str_convert(const std::string& string_orig) {
std::cerr << "dbg: orig: " + string_orig + "\n";
std::vector<FriBidiChar> fribidi_in_char(MAX_STR_LEN);
const FriBidiStrIndex fribidi_len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, string_orig.c_str(),
string_orig.size(), fribidi_in_char.data());
assert(fribidi_len < MAX_STR_LEN);
fribidi_in_char.resize(fribidi_len);
fprintf(stderr, "len is %i\n", fribidi_len);
//
FriBidiCharType fribidi_pbase_dir = FRIBIDI_TYPE_LTR;
std::vector<FriBidiChar> fribidi_visual_char(fribidi_len + 1);
const fribidi_boolean stat = fribidi_log2vis(fribidi_in_char.data(), fribidi_len, &fribidi_pbase_dir,
fribidi_visual_char.data(), NULL, NULL, NULL);
fprintf(stderr, "stat is: %d\n", stat);
//
if (stat) {
//
std::string string_formatted_ptr(MAX_STR_LEN, 0);
const FriBidiStrIndex new_len = fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8, fribidi_visual_char.data(),
fribidi_len, string_formatted_ptr.data());
assert(new_len < MAX_STR_LEN);
string_formatted_ptr.resize(new_len);
fprintf(stderr, "new_len is: %d\n", new_len);
//
return string_formatted_ptr;
}
return string_orig;
}
int main() {
const std::string orig = "אריק איינשטיין";
std::cerr << "main: orig: " << orig << "\n";
const auto ret = fribidi_str_convert(orig);
std::cerr << "main: transformed: " << std::setw(10 + orig.size()) << ret << "\n";
}
и выходы:
$ g++ -lfribidi 1.cpp && ./a.out
main: orig: אריק איינשטיין
dbg: orig: אריק איינשטיין
len is 14
stat is: 2
new_len is: 27
main: transformed: ןייטשנייא קירא
Зная, что FriBidiChar
— это uint32_t, а fribidi использует UTF-32 для внутреннего использования, а wchar_t
в Linux — это UTF-32, было бы предпочтительнее использовать std::wstring
(или wchar_t
), чтобы узнать, сколько памяти нужно выделить. Вы также можете подсчитать кодовые точки во входной строке UTF-8, а затем предварительно рассчитать длину представления UTF-8 для fribidi_visual_char
, чтобы выделить память для fribidi_unicode_to_charset
.
FriBidiChar fribidi_in_char;
FriBidiStrIndex fribidi_len = fribidi_charset_to_unicode(
FRIBIDI_CHAR_SET_UTF8,
string_orig.c_str(),
string_orig.size(),
&fribidi_in_char
);
Последний параметр должен быть указателем на буфер, достаточно большой для записи результатов. Из-за этого нарушения fribidi_charset_to_unicode
будет записывать свой вывод в любые байты, оказавшиеся в памяти после этого символа, что является неопределенным поведением. Иногда приложение может давать сбой, иногда оно дает правильные результаты, иногда — неправильные результаты, а иногда оно стирает все файлы в домашнем каталоге пользователя. Решение этой проблемы состоит в том, чтобы просто передать nullptr
в качестве вывода, и в этом случае он вычисляет длину, но не записывает.
FriBidiChar fribidi_visual_char;
fribidi_boolean stat = fribidi_log2vis(
/* input */
&fribidi_in_char,
fribidi_len,
&fribidi_pbase_dir,
/* output */
&fribidi_visual_char, //here
NULL,
NULL,
NULL
);
Предпоследний параметр visual_str
должен быть указателем на буфер длины fribidi_len
, но вы просто передаете указатель на один символ. Из-за этого нарушения fribidi_log2vis
будет записывать свой вывод в любые байты, оказавшиеся в памяти после этого символа, что является неопределенным поведением. Иногда приложение может давать сбой, иногда оно дает правильные результаты, иногда — неправильные результаты, а иногда оно стирает все файлы в домашнем каталоге пользователя.
Вам нужно выделить достаточно памяти для выполнения своей работы:
std::basic_string<FriBidiChar> fribidi_visual_chars(fribidi_len+1, '\0');
fribidi_boolean stat = fribidi_log2vis(
/* input */
&fribidi_in_char,
fribidi_len,
&fribidi_pbase_dir,
/* output */
fribidi_visual_chars.data(), //here
NULL,
NULL,
NULL
);
И снова с string_formatted_ptr
и fribidi_unicode_to_charset
.
std::string fribidi_str_convert(const std::string& string_orig)
FriBidiStrIndex fribidi_len = fribidi_charset_to_unicode(
FRIBIDI_CHAR_SET_UTF8,
string_orig.c_str(),
string_orig.size(),
nullptr);
std::basic_string<FriBidiChar> fribidi_visual_chars(fribidi_len+1, '\0');
fribidi_charset_to_unicode(
FRIBIDI_CHAR_SET_UTF8,
string_orig.c_str(),
string_orig.size(),
fribidi_visual_chars.data());
// I'm uncertain how to calculate the length,
// so I assumed its the same as the input :(
std::basic_string<FriBidiChar> fribidi_visual_char(fribidi_len+1, '\0');
fribidi_boolean stat = fribidi_log2vis(
/* input */
&fribidi_in_char,
fribidi_len,
&fribidi_pbase_dir,
/* output */
fribidi_visual_char.data(),
NULL,
NULL,
NULL);
if (!stat) return string_orig;
FriBidiStrIndex new_len = fribidi_unicode_to_charset(
FRIBIDI_CHAR_SET_UTF8,
&fribidi_visual_char,
fribidi_len,
nullptr);
std::string string_formatted_out(new_len+1, '\0');
fribidi_unicode_to_charset(
FRIBIDI_CHAR_SET_UTF8,
&fribidi_visual_char,
fribidi_len,
string_formatted_out.data());
return string_formatted_out;
}
};
это просто}
.char string_formatted_ptr;
?? Это один персонаж...FriBidiChar fribidi_in_char;
Это тоже один персонаж. Вы не можете записать строку в один символ памяти.#include <stdio.h>
Используйтеcstdio
.I saw many examples online that use std::wstring
Где? Пожалуйста, разместите их.