FOnline Engine
Current master GitHub
Документация Docs/ru/how-to/content/font-format.md

Форматы шрифтов и компоновка текста

FOnline отрисовывает растровые шрифты, описанные текстовым форматом Engine .fofnt или бинарным форматом BMFont v3 .fnt. Дескрипторы без изменений копируются в запечённые ресурсы, связанные изображения проходят обычный конвейер запекания изображений, а клиентские скрипты привязывают пути дескрипторов к слотам FontType.

Используйте это руководство для решений по авторингу и интеграции. Точный контракт текущей ревизии приведён в сгенерированном справочнике font-format, на отдельных страницах форматов, FOFNT, BMFont, привязки, компоновки, отрисовки и валидации, а также в канонической JSON-модели.

Область и источники истины

Поведение определяют следующие исходники:

  • Source/Client/FontManager.cpp и .h: разбор дескрипторов, метрики глифов, масштабирование при привязке, подготовка атласа, компоновка текста, отрисовка и краткоживущий кэш форматирования;
  • Source/Scripting/ClientGlobalScriptMethods.cpp: Game.BindFont, Game.GetTextInfo, Game.GetTextLines и Game.DrawText;
  • Source/Common/Settings.inc и Source/Tools/RawCopyBaker.cpp: выбор и доставка ресурсов без преобразования;
  • Source/Client/Updater.cpp: встроенная зависимость от шрифта по умолчанию;
  • Resources/Core/Fonts/: поставляемые примеры обоих runtime-дескрипторов и вспомогательных файлов авторинга BMFont.

BuildTools/FontFormatInterface.json является структурированным контрактом, опирающимся на исходники. BuildTools/docs_font_format.py выводит из актуального кода диспетчеризацию расширений, настройки raw-copy, ключи и максимальную версию FOFNT, бинарные константы и знаковые поля BMFont, слоты и флаги шрифта, диапазон масштаба, атлас, срок жизни кэша, путь обновлятора и список поставляемых дескрипторов. Генератор отклоняет расхождение исходников и манифеста и строит справочник.

Эта страница описывает переиспользуемое поведение Engine. Встраивающий проект владеет файлами шрифтов, расширениями FontType, назначениями GUI, типографикой, покрытием языков, лицензиями, снимками разных backend и порогами приёмки. Проектная документация может ссылаться сюда, но не должна переопределять контракт парсера.

Поддерживаемые ресурсы

Через Game.BindFont runtime принимает ровно два регистрозависимых суффикса:

Суффикс Роль в runtime Примечания
.fofnt Текстовый дескриптор Engine Явно задаёт изображение, строки и записи глифов.
.fnt Бинарный дескриптор BMFont v3 Только бинарный формат, одна страница текстуры, отступ в один пиксель.
.bmfc Не используется Конфигурация инструмента BMFont; по умолчанию копируется без преобразования, но Game.BindFont её не разбирает.

Клиент не загружает текстовые или XML-дескрипторы BMFont, TTF, OTF и другие векторные шрифты во время выполнения. Перед поставкой преобразуйте или растрируйте такие источники.

Дескриптор и изображение доставляются отдельно:

  1. Оставьте fofnt и fnt в Baking.RawCopyFileExtensions. RawCopyBaker сохраняет байты и путь каждого дескриптора.
  2. Поместите связанное растровое изображение в пакет ресурсов. Оно запекается и загружается через конвейер форматов изображений и спрайтов.
  3. Сохраните относительное расположение дескриптора и изображения. Оба загрузчика объединяют имя изображения с каталогом дескриптора.
  4. Привяжите слот на клиенте до любых измерений или отрисовки этим шрифтом.

Успешный raw-copy доказывает только доставку дескриптора. Он не доказывает корректность изображения, прямоугольников глифов, покрытия языка, обводок или композиции GUI.

Минимальный FOFNT

FOFNT разбирается по токенам, разделённым пробелами. Первым разобранным ключом должен быть Version. Для текущих ресурсов следует задавать версию 2; клиент отклоняет значения больше 2. Минимальный дескриптор выглядит так:

Version 2
Image Example.png*
LineHeight 14
YAdvance 2

Letter ' '
  PositionX 1
  PositionY 1
  Width 1
  Height 1
  OffsetX 0
  OffsetY 0
  XAdvance 5

Letter 'A'
  PositionX 4
  PositionY 1
  Width 9
  Height 12
  OffsetX 0
  OffsetY 0
  XAdvance 10

End

Завершающий * в Image включает нормализацию в оттенки серого. Опустите его, если необходимо сохранить исходные RGB растрового изображения. Поле Image обязательно и задаётся относительно дескриптора. Текущий loader достигает image_name.back() без явной проверки пустого значения, поэтому проверяйте поле до runtime и не рассчитывайте на диагностическое исключение при binding.

Letter декодирует одну кодовую точку UTF-8 после первого апострофа. Следующие ключи метрик изменяют текущий глиф до следующего Letter. Повторная кодовая точка заменяет предыдущую запись. Неизвестные ключи игнорируются, поэтому опечатки особенно опасны: дескриптор может привязаться с нулевыми метриками по умолчанию. Считайте предупреждения, отсутствующие глифы и визуальное смещение ошибками ресурса.

# и ; удаляются только тогда, когда встречаются в текущем ключе, отделённом пробелами. Не используйте их как полноценный синтаксис комментариев строки. End останавливает разбор и должен завершать каждый авторский дескриптор.

Метрики FOFNT

У каждого глифа есть видимый прямоугольник и метрики курсора:

  • PositionX, PositionY: верхний левый пиксель видимого прямоугольника;
  • Width, Height: видимые размеры без однопиксельной рамки выборки;
  • OffsetX, OffsetY: знаковые выносы в координатах Engine. Отрисовка начинается в позиции курсора минус смещение, поэтому положительные значения двигают изображение влево/вверх, а отрицательные вправо/вниз;
  • XAdvance: знаковое горизонтальное продвижение курсора после кодовой точки;
  • LineHeight: видимая высота строки. Ноль или отсутствие поля выводит максимум высот глифов после необязательного масштабирования;
  • YAdvance: дополнительный интервал между строками.

Задавайте глиф пробела явно. Его XAdvance становится SpaceWidth; без него пробелы и табуляции могут иметь нулевую ширину. Табуляция продвигает курсор на четыре SpaceWidth. Кернинговые пары не представлены и не применяются.

Оставляйте не менее одного прозрачного пикселя вокруг каждого видимого глифа и края изображения. Отрисовщик расширяет координаты текстуры и геометрию на один пиксель со всех сторон. Эта рамка хранит сглаженные краевые пиксели и даёт необязательному генератору обводки место для расширения без протекания в соседние глифы.

Бинарный BMFont

Настройте экспортёр BMFont следующим образом:

  • бинарный формат версии 3;
  • ровно одна страница текстуры;
  • отступы сверху/справа/снизу/слева = 1/1/1/1;
  • блоки Info, Common, Pages и Chars в стандартном порядке;
  • имя изображения страницы относительно файла .fnt.

Клиент ожидает записи символов длиной 20 байт. BMFont определяет xoffset, yoffset и xadvance как знаковые 16-битные little-endian значения, а отрицательные выносы встречаются в поставляемых шрифтах. Текущий loader, однако, читает все три поля через GetLEUInt16, поэтому отрицательные значения интерпретируются как числа около 65535 и могут увести глиф далеко от нужного места. Считайте это известным ограничением runtime и избегайте отрицательных метрик до отдельной правки кода.

Загрузчик удаляет отступ экспортёра из каждой записи: сдвигает X/Y на один, вычитает два из ширины/высоты, меняет знак выносов для соглашения Engine и добавляет один к X advance. BMFont Common lineHeight участвует в преобразовании вертикального выноса, но не копируется напрямую. Engine LineHeight использует видимую высоту глифа W, если он есть, иначе Common base; YAdvance равен половине результата.

Каждая бинарная привязка BMFont нормализуется в оттенки серого и получает копию атласа с обводкой. Runtime игнорирует кернинг и не поддерживает несколько страниц текстуры или другой порядок блоков. Перед изменением экспортёра проверьте сгенерированный контракт BMFont.

Привязка слотов шрифта

Engine объявляет один слот:

enum FontType
{
    Default = 0
}

Встраивающий проект может расширить FontType аннотацией enum для codegen и привязать каждый слот при инициализации клиента:

Game.BindFont(FontType::Default, "Fonts/Default.fofnt");
Game.BindFont(FontType::Big, "Fonts/Big.fofnt", 0.8f);
Game.BindFont(FontType::Numbers, "Fonts/Numbers.fofnt");

Точный синтаксис расширения enum принадлежит настройке сгенерированного API встраивающего проекта. Слоты являются целочисленными индексами, а не псевдонимами путей. Измерение или отрисовка незагруженного, отрицательного или выходящего за границы слота создаёт исключение.

Оба типа дескрипторов привязываются к AtlasType::IfaceSprites. Повторная привязка слота заменяет шрифт, перестраивает данные текстуры и очищает кэш компоновки. Встроенный обновлятор отдельно пытается привязать FontType::Default из Fonts/Default.fofnt, пропуская уже загруженный слот, поэтому этот ресурс является частью стандартного контракта хоста.

Масштаб при привязке

Game.BindFont принимает defaultScale со значением по умолчанию 1.0. Оно должно быть конечным и находиться в (0, 1]. Клиент намеренно не увеличивает растровый шрифт: создайте больший исходный атлас и уменьшайте его для малых слотов.

Масштабирование выполняется один раз при привязке:

  1. Каждый глиф передискретизируется усреднением площади внутри своего прямоугольника с цветом, взвешенным по альфа-каналу.
  2. Исходный прямоугольник очищается, а уменьшенное изображение записывается в ту же верхнюю левую позицию, не позволяя соседним глифам протекать в него.
  3. Размер глифа, выносы, продвижение, высота строки, ширина пробела и межстрочный интервал округляются до целых целевых метрик.
  4. К масштабированному результату применяются нормализация серого и расширение обводки.

В TextFormat нет независимого масштаба шрифта для виджета. Если проекту нужно несколько размеров, привязывайте отдельные слоты. Проверяйте каждый масштаб через Game.GetTextInfo и видимый текст: округление целых может изменить перенос и посадку на базовую линию.

TextFormat и компоновка

TextFormat содержит Font, битовую маску FontFlag и неотрицательный SkipLines. Точные значения флагов перечислены в сгенерированном справочнике компоновки. Важные взаимодействия:

  • по умолчанию при конечной ширине строка переносится по последнему пробелу или табуляции; слишком длинный токен разрывается в точке переполнения;
  • NoWrap обрывает отрисовку при первом переполнении ширины. Это режим только отрисовки: Game.GetTextInfo продолжает обычный перенос, поэтому измерение не определяет итоговую обрезанную подстроку;
  • TruncateLine удаляет переполняющие глифы до следующего авторского перевода строки;
  • CenterX и AlignRight позиционируют каждую строку независимо;
  • CenterY и AlignBottom позиционируют видимый блок текста по вертикали;
  • SkipLines удаляет начальные строки, а с AlignBottom конечные;
  • KeepTail удаляет начальное переполнение, сохраняя новые помещающиеся строки;
  • Justify распределяет оставшуюся ширину между пробелами перенесённых строк. Табуляции остаются равными четырём ширинам пробела;
  • Bordered выбирает сгенерированную текстуру с обводкой.

Нулевая ширина или высота для форматировщика означает отсутствие ограничения по соответствующему измерению. Публичные функции подсчёта строк отклоняют неположительные размеры, поэтому для переносимого измерения используйте явно положительные прямоугольники GUI.

Форматировщик декодирует кодовые точки UTF-8. Некорректные последовательности и отсутствующие в выбранном шрифте кодовые точки имеют нулевое продвижение и не дают запасного глифа. Шрифт без нужного языкового символа может схлопнуть слово без жёсткой ошибки runtime. Покрытие глифов должно быть отдельным проектным gate.

Измерение и отрисовка

При измерении и отрисовке используйте один слот, флаги, ширину и высоту:

TextFormat format;
format.Font = FontType::Default;
format.Flags = FontFlag::CenterX | FontFlag::CenterY;

isize resultSize;
int resultLines;
Game.GetTextInfo(text, boxSize, format, resultSize, resultLines);
Game.DrawText(text, boxPos, boxSize, color, format);

Game.DrawText доступен только в событии отрисовки интерфейса. Отрицательная ширина или высота сдвигает начало прямоугольника и преобразуется в положительный размер до компоновки. Прозрачный цвет выбирает стандартный белый цвет текста Engine.

За исключением NoWrap, действующего только при отрисовке, измерение и отрисовка используют один форматировщик, метрики строк, пропуски, масштаб и продвижения глифов. GetTextInfo возвращает максимальную ширину строки, высоту видимого блока и число видимых строк. Ключ кэша включает текст, слот, флаги, пропуски, размеры прямоугольника, цвет и режим форматирования; записи истекают после трёх неиспользованных кадров и сбрасываются при замене шрифта. Не полагайтесь на идентичность или срок жизни кэша.

Цвет и эффекты

Шрифт начинает работу с общим эффектом шрифта Engine. Проект может заменить общий эффект или выбрать подтип EffectType::Font для отдельного слота. Пустое переопределение слота возвращает его к текущему общему эффекту. Синтаксис и backend-валидация описаны в формате эффектов.

Встроенные теги отрисовщика используют упакованный порядок Engine BBGGRR / AABBGGRR с необязательным префиксом 0x; @color@ восстанавливает предыдущий цвет. Корректные теги удаляются до переноса. При NoColorize они также удаляются, но цвет не применяется. Некорректные теги остаются обычным текстом. Точные формы и общая граница авторинга строк описаны в тексте и локализации; не дублируйте эти теги в проектной грамматике локализации.

Рекомендуемые проектные практики

Держите типографику компактной и управляемой данными:

  1. Определяйте семантические слоты для основного текста, заголовков, компактных чисел и отладки, а не отдельный слот для каждого виджета.
  2. Создавайте наибольший нужный растр семейства и привязывайте проверенные уменьшенные варианты. Не рассчитывайте на масштабирование при компоновке.
  3. Включайте все кодовые точки основного и запасного языков, пунктуацию, цифры и символы, используемые игровым процессом, чатом, консолью и обновлятором.
  4. Проверяйте в одной области review дескриптор, изображение, сведения о лицензии и происхождении, привязку слота и визуальную тестовую сцену.
  5. Измеряйте динамические подписи с реальной локализованной строкой и слотом; не рассчитывайте панель по английской заглушке или числу символов.
  6. Поддерживайте матрицу снимков обычной/обведённой отрисовки, всех масштабов, узкого переноса, используемых выравниваний и самых длинных локализованных подписей на каждом поддерживаемом backend.

Процесс валидации

После изменения парсера Engine, метрик, компоновки или скриптовой привязки:

python BuildTools\docs_font_format.py --write
python -m unittest BuildTools.tests.test_docs_font_format
python BuildTools\docs_contract_diff.py --check
cmake --build <build-dir> --config RelWithDebInfo --target RunUnitTests

После изменения шрифта во встраивающем проекте:

  1. Перегенерируйте проектный код при изменении FontType.
  2. Запеките дескриптор и изображение вместе.
  3. Запустите целевые тесты измерения текста и компоновки GUI для каждого изменённого слота или масштаба.
  4. Откройте видимый клиент и проверьте обычные, обведённые, цветные, перенесённые, выровненные и локализованные строки.
  5. Проверьте журналы на исключения дескриптора, изображения, атласа, эффекта и скрипта.
  6. В том же изменении обновите проектный каталог шрифтов и документацию GUI/локализации.

Целевые сгенерированные проверки доказывают описанный контракт исходников. Только встраивающий проект может доказать покрытие глифов, типографику, посадку в UI, поведение backend отрисовки и права на ресурсы.

Введите запрос.