Введение
В этой статье я поделюсь опытом как в собственный TextBox была добавлена поддержка двунаправленного текста с правильным отображением диакритиков с использованием FriBidi и HarfBuzz. Это вторая статья на эту тему, а первой была Добавление поддержки двунаправленного текста в собственный TextBox. В ней я описывал особенности добавления арабского в собственный текст с использованием FriBidi.
В чём проблема?
Диакрити́ческие зна́ки (диакри́тики (профессионально-жаргонное)) в типографике — элементы письменности, модифицирующие начертание знаков и обычно набираемые отдельно. В предыдущем предложении знаки ударения над и́ и а́ — это диакритические знаки. Например, в русском языке диакритиками можно считать две точки над «ё» и кратка над «й». Но добавление этих диакритиков привело к созданию новых букв, хотя для ё две точки часто опускаются.
В большинстве языков при работе с текстом особых проблем с рендерингом диакритиков не возникает (если конечно вы не указываете ударение над каждой буквой), т.к. буквы с диакритиком — это или отдельная буква в алфавите или в файлах шрифтов они идут как отдельный символ. Другими словами, TextBox-у не надо отдельно размещать диакритики над буквами.
Но в арабском языке (и например, в хинди) не всё так просто. В арабском языке огласо́вки являются диакритическими знаками. Они могут использоваться почти с каждой буквой и даже у одной буквы может быть несколько огласок.
Чёрным цветом изображены буквы арабского алфавита, серым — огласовки (диакритики).
Как вы понимаете, никто не перебирал все возможные комбинации букв и огласок и не заводил для каждой комбинации отдельный символ. То есть, для правильного рендера арабского текста необходимо отрисовать арабскую букву и отдельно над или под ней отрисовать диакритик.
FreeType, который мы использовали, позволяет получить изображение диакритика из файла шрифта и даже сообщает нам сдвиги. Но эти сдвиги некорректные, т.е. по одному символу невозможно понять, как расположить диакритик. Ниже показательный пример — несколько диакритиков над буквой. Для правильного позиционирования необходимо проанализировать весь текст.
Для вычисления позиции диакритиков над буквами мы использовали библиотеку HarfBuzz. Библиотека позволяет получить номера глифов в шрифте и их сдвиги для дальнейшей отрисовки.
Как использовать HarfBuzz
HarfBuzz получает на вход шрифт и строку, а возвращает позицию каждой буквы и дополнительную информацию (например, номер глифа).
hb_buffer_t *buf; // harfbuzz буфер. hb_buffer_create/hb_buffer_destroy
hb_font_t *hb_ft_font; // harfbuzz шрифт, для создания используйте hb_font_create, для уничтожения hb_font_destroy
hb_script_t script; // Скрипт текущего текста. Используйте hb_unicode_script для получения скрипта.
hb_direction_t dir = hb_script_get_horizontal_direction(script);
hb_buffer_set_direction(buf, dir); // Справа налево или слева на право
hb_buffer_set_script(buf, script);
hb_buffer_add_utf32(buf, (const uint32_t*)text,length, 0,length); // Добавляем наш текст в harfbuzz буффер.
hb_shape(hb_ft_font, buf, NULL, 0); // расчёт
unsigned int glyph_count = 0;
hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(buf, &glyph_count); // Получаем информацию о глифах.
hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(buf, &glyph_count); // Получаем позицию глифов.
Стоит отметить, что приведённый код необходимо применять только к тексту, использующему одинаковый шрифт и имеющий одинаковый скрипт. Для реализации разбиения текста на такие части можно использовать функцию hb_unicode_script, которая возвращает скрипт символа.
Так как перед нами стояла задача поддержки не просто арабского, но и двунаправленного текста (например, арабский и латинский может присутствовать в одной строчке), мы использовали FriBidi для правильного позиционирования. Но это более подробно было описано в первой статье Добавление поддержки двунаправленного текста в собственный TextBox.
Изменения в TextBox-е
Итак, Текст бокс уже поддерживал двунаправленный текст. Символы хранятся в памяти в порядке ввода, но каждому из них соответствовала позиция в порядке рендера.
С добавлением даикритиков ситуация немного усложнилась, т.к. одной букве при рендере могло соответствовать несколько введённых символов. Для того чтобы код работы с позиционированием курсора мог работать независимо от диакритиков, буквы пришлось немного усложнить. Теперь каждая буква хранила в себе список глифов, которые в неё входят.
При таком подходе упростилась реализация функций редактирования, включая копирование и вставку. Но такой подход не даёт возможности удалить отдельный диакритик, так как курсор можно поставить только перед или за буквой.
Пример
Пример рендера двунаправленного текста вы можете найти здесь GitHub/ex-sdl-freetype-harfbuzz-fribidi. В примере используется: SDL2 — для создания окна визуализации; Freetype — для рендера букв; fribidi — для правильного позиционирования; harfbuzz — для получения глифов и их позиций.
Disclaimer
Да, мы пишем свой велосипед, поэтому реализуем свой TextBox с нуля. И мы не использовали Pango, потому что с ним был неудачный опыт раньше. Может быть, с Pango это было бы сделать легче.
Полезные ссылки
- behdad.org/text — о рендеринге текста
- www.freedesktop.org/wiki/Software/HarfBuzz — библиотека HarfBuzz.
- fribidi.org — библиотека FriBidi.
- ru.wikipedia.org/wiki/Огласовки_в_арабском_письме — о диакритиках в арабском письме.
- ru.wikipedia.org/wiki/Диакритические_знаки — о диакритических знаках.
- ex-sdl-freetype-harfbuzz-fribidi — мой пример использования fridibi + harfbuzz, был основан на https://github.com/lxnt/ex-sdl-freetype-harfbuzz.
- www.unicode.org/reports/tr9 — алгоритм отображения двунаправленного текста.
- www.pango.org — библиотека Pango.
Автор: UnickSoft