Как использовать fribidi с std::string?

Я пытаюсь написать функцию, которая будет запускать алгоритм фрибиди на 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 - почти в два раза больше исходной длины?

}; это просто }. char string_formatted_ptr; ?? Это один персонаж... FriBidiChar fribidi_in_char; Это тоже один персонаж. Вы не можете записать строку в один символ памяти. #include <stdio.h> Используйте cstdio. I saw many examples online that use std::wstring Где? Пожалуйста, разместите их.
KamilCuk 17.04.2023 21:39

@KamilCuk Я добавил ссылку на примеры, которые видел в Интернете. Я не уверен, что вы имеете в виду под этой опечаткой }; vs } - этот код является точной копией того, что было успешно скомпилировано для меня. У меня были другие ошибки, когда я пытался использовать char *string_formatter_ptr;...

Doron Behar 17.04.2023 22:00

Вы можете сделать };;;;;;;;;;;;;;, если хотите, ; просто бесполезен. Просто }. В вашей программе есть };.

KamilCuk 17.04.2023 22:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это очень неправильно. Вы не можете ожидать, что сохраните строку в один символ. Чар есть чар. Это не указатель. Не строка. Не забудьте скомпилировать свои программы с помощью -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. Даже программа fribidyhttps://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.


При беглом взгляде на их API я думаю, что вам нужно следующее:
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;
}

Другие вопросы по теме