Skip to content

UTF-8: ширина колонок в выводе считается в байтах (%-20.20s и подобные) #3797

Description

@bylins

После перехода на UTF-8 (#3681) остался класс мест, где ширина колонки считается в байтах: форматы вида %-20.20s, %25s, %60s. Русская буква занимает два байта, поэтому такое поле ведёт себя не так, как задумано.

Два симптома

Колонка не выравнивается. Ширина без точности (%25s, %-20s) только дополняет строку до N байт. Русское слово из 12 символов — это 23 байта, дополнять нечем, поле переполняется, и всё, что правее, съезжает. Ровно это видно в выводе экипировки, где рядом стоят предметы с русскими и латинскими именами.

Строка рвётся посреди буквы. Ширина с точностью (%-20.20s) режет по N байт, и разрез попадает в середину двухбайтовой буквы:

'золотое кольцо'  -> символов 14, байт 27
%-20.20s          -> 'золотое ко' + половина буквы 'л'

Клиент получает битый байт и рисует «кракозябру» или проглатывает символ — в зависимости от клиента. Похоже, отсюда же жалоба Веледара на кривые строки.

Как это выглядит на живых данных

Меню магии в oedit (%-30.30s, четыре колонки) уходит клиенту так:

 1) легкое исцелени�  2) серьезное исцел�  3) критическое исц�  4) исцеление
 5) групповое исцел�  6) великое исцелен�  7) клич мощи               8) увеличить жизнь

А задумано так:

 1) легкое исцеление             2) серьезное исцеление          3) критическое исцеление        4) исцеление
 5) групповое исцеление          6) великое исцеление            7) клич мощи                    8) увеличить жизнь

Оба дефекта в одной строке: длинные имена режутся посреди буквы, короткие добиваются до 30 байт (15 символов вместо 30) и колонка пляшет. До флипа всё было ровно: в KOI8-R байт равнялся символу.

По трём меню, где удалось взять реальные названия из lib/cfg/messages/ru:

меню имён колонка пляшет обрезается разрез в середине буквы
магия, %-30.30s 248 186 62 60
умения, %-20.20s 93 41 52 23
способности, %-20.20s 156 30 126 86

Инвентаризация

Прошёл по всем форматам с шириной у %s (tools/audit_utf8_migration.py --category printf-width плюс свой проход):

мест
всего форматов с шириной 99
из них do_toggle.cpp%-3s под ON/OFF 41 (не трогать, аргумент ASCII)
остальные 58
из них с русским аргументом (имя, описание, причина, метка) 35

Разбивка по областям:

  • OLC-менюoedit.cpp (14), medit.cpp (5), olc.cpp, zedit.cpp, redit.cpp, obj_sets_olc.cpp, dg_olc.cpp: %-20.20s и %25s под названия умений, заклинаний, способностей, типов зон. Видят билдеры и иммы, каждый день.
  • Команды боговdo_tabulate.cpp (6, %60s под имя предмета), do_stat.cpp (6), do_show.cpp (4), dg_scripts.cpp (4, имена и значения переменных триггеров).
  • Игрокамim.cpp:1021 (%30s под название рецепта), modify.cpp:1014 (%30s под название заклинания), do_levels.cpp (звания), title.cpp:277 (%.*s при разборе титула).
  • Списки бановban.cpp:316/352/394, %-25.25s под имя и %-16.16s под причину: причина по-русски, режется по байтам.

Чем чинить

В движке уже есть готовое:

  • native_text::pad_right(std::string_view, std::size_t width) — дополнение до ширины в символах. Им уже пользуются: do_toggle.cpp для «краткий/полный», do_stat.cpp:625 (там и комментарий стоит: «Ширина поля -- в символах: printf меряет %-21s в байтах»).
  • native_text::char_offset(s, n) — байтовое смещение n-го символа, то есть s.substr(0, char_offset(s, n)) обрезает по символам, не разрывая букву.

То есть правка механическая: "%-20.20s" + аргумент → "%s" + native_text::pad_right(utils::RemoveColors? ...) с обрезкой через char_offset. Отдельно стоит решить, что делать с цветовыми кодами &G: они тоже занимают байты, но не занимают ширину, — там, где имя может быть цветным, ширину надо мерить после снятия кодов.

Что предлагаю

  1. OLC-меню — там бардак виден глазами и билдеры смотрят на него каждый день. Правка однотипная, хорошо ложится одним проходом по файлу.
  2. То, что видят игроки: im.cpp, modify.cpp, do_levels.cpp, title.cpp.
  3. Списки банов и команды богов — последними.

do_toggle.cpp в список не входит: там 41 формат, но все под ON/OFF, это ASCII и работает верно. Трогать его не нужно, иначе диф раздуется на ровном месте.

Мелочь по инструменту

tools/audit_utf8_migration.py декодирует сниппеты из KOI8-R — после флипа он печатает вместо кода мусор. Сама выборка мест верная, читать неудобно: одну строку в нём поправить.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions