Войти
K5EngineСтатьи

Текстовые объекты

Автор:

Введение.

В данной статье речь пойдёт о системе вывода текста в движке K5Engine. Будут описаны основные классы, системы а так же даны примеры работы с текстом. Так же будет рассмотрена вспомогательная система для динамической генерации шрифтов FreeTypeFontSystem — обвёртку над библиотекой FreeType.

Общие принципы работы с текстом.

Работа с текстовыми объектами подобна работе с спрайтами, та же позиция, угол наклона, задание устройства и помещение текста в очередь рисования. Сам текстовый объект  это по сути список спрайтов-букв с генерацией этих самых букв в определённом порядке и с заданными параметрами. Текстуры букв берутся из шрифтов — текстурного или динамически генерируемого. Собственно для удобного управления этими самыми шрифтами предназначены специальные списки. Далее разберём каждый элемент системы по порядку.

Шрифты.

Основным элементом шрифта является глиф — текстура отдельной буквы с описанием отступов и смещений для корректного расположения в строке. В движке это класс TGlyph, его заголовочный файл располагается по пути [K5Engine][Core][GraphicSystem][Text][Glyph.h]. В принципе рассматривать детально этот класс нет смысла, работать с ним на прямую не придётся. Глифы в свою очередь помещаются в список TGlyphList, который используется уже в классе шрифта TBaseFont и его наследниках. При использовании группы шрифтов удобно пользоваться списком TFontList. Он аналогичен текстурным спискам, есть методы для получения шрифта — Get , для добавления — Add, для отчисти - Clear. Заголовочный файл списка шрифтов можно найти по пути [K5Engine][Core][GraphicSystem][Text][FontList.h].

Текстурный шрифт.

Как упоминалось ранее, в движке можно использовать два вида шрифтов — текстурного и генерируемого. Оба варианта имеют свои плюсы и минусы. Для начала разберём текстурный шрифт. Это обычный текстурный атлас с определённым описанием расположения, размеров и отступов у глифов, его класс — TBitmapFont, который в свою очередь наследуется от TBaseFont. Его заголовочный файл [K5Engine][Core][GraphicSystem][Text][BitmapFont.h]. Разберём методы  TBitmapFont более подробно:

class TBitmapFont:public TBaseFont
{
  protected:
    vector<TBitmapFontCell> GlyphsInfo;
    TTexturePointerList Textures;
  protected:
    void ToClear();
  public:
    TBitmapFont();
    virtual ~TBitmapFont();

    pTGlyph GetGlyph(const wchar_t &Symbol);

    void AddFontTexture(const pTTexture &Val);

    void AddGlyphInfo(const TBitmapFontCell &Val);

    void AddGlyphInfo(  const wchar_t &CharVal, const int &TextureIdVal,
          const int &RectXVal, const int &RectYVal,
          const int &RectWidthVal, const int &RectHeightVal,
          const int &CharShiftXVal, const int &CharShiftYVal,
          const int &TextCursorShiftVal);

    void ClearGlyphsInfo();
};
Как видно TBitmapFont наследуется от класса базового шрифта TBaseFont, Далее по порядку по каждому методу и члену класса:
vector<TBitmapFontCell> GlyphsInfo; - список параметров каждого глифа, это символ, позиция и размер части текстуры, смещение символа при выводе в строке и смещение курсора строки. Ознакомиться с заголовочным файлом можно по пути [K5Engine][Core][GraphicSystem][Text][BitmapFontCell.h].
TTexturePointerList Textures; - список указателей на текстурные карты, которые используются в шрифте. Так как иногда не все символы влазят на одну текстуру, можно загрузить несколько текстур и передать их в TBitmapFont.
pTGlyph GetGlyph(const wchar_t &Symbol); - метод получения глифа, наследуется из TBaseFont. По передаваемому символу ищет глиф и возвращает его или генерирует исключение. Фактически это ключевой метод, с которым работает текст.
void AddFontTexture(const pTTexture &Val); - добавление текстурной карты. Просто добавляется указатель на ранее загруженную текстуру. В последствии он используется в глифах.
void AddGlyphInfo(....); - два метода, в одном добавляется структура, описывающая параметры глифа, во втором передаются параметры глифа: символ, индекс используемой текстуры, позиция и размер участка текстуры с изображением символа, смещение символа и смещение курсора строки.
void ClearGlyphsInfo(); - отчищает GlyphsInfo. Используется в редких случаях, так как в ручную отчищать шрифты обычно не надо, они удаляют свои данные при разрушении.

Далее рассмотрим алгоритм инициализации текстурного шрифта, конечно могут быть вариации инициализации в зависимости от задач. Нужно выполнить несколько шагов:
Загрузить текстурные карты шрифта.
Создать по указателю на TBitmapFont класс шрифта или объявить класс как статический объект, если используется всего пару шрифтов в приложении и нет надобности хранить их в списке шрифтов.
Установить шрифту имя, добавить указатели на текстуры с символами.
Добавить описания глифов. Вместо ручного добавления параметров глифов (что довольно не удобно), можно использовать их описание из файла при помощи класса TBitmapFontLoader. 
Передать шрифт в список шрифтов.

Для более удобной инициализации текстурного шрифта можно использовать специальные файлы конфигурации. Для их разбора используется класс  TBitmapFontLoader, заголовочный файл которого находится по пути [K5Engine][Cover][BitmapFontLoader.h].
Рассмотрим подробно этот класс.

class TBitmapFontLoader
{
  protected:
    TExceptionGenerator Exception;

    TTexturePointerList  TexturePointers;
    pTTextureListManager Textures;
  protected:
    inline wstring CutData(const wstring &Str);

    inline void ParceInfoStr   (const wstring &Str, const pTBitmapFont &Font);
    inline void ParceTextureStr(const wstring &Str, const pTBitmapFont &Font);
    inline void ParceCellStr   (const wstring &Str, const pTBitmapFont &Font);
    inline void ParceStr       (const wstring &Str, const pTBitmapFont &Font);
  public:
    TBitmapFontLoader();
    ~TBitmapFontLoader();

    virtual pTBitmapFont Run(const wstring &FileName);

    void SetTextures(const pTTextureListManager &Val);
    pTTextureListManager GetTextures() const;

    void AddTexturePointer(const pTTexture &Val);
    void ClearTexturePointers();
};
Данный класс загружает из указанного файла конфигурацию и создаёт текстурный шрифт. Для этого используется метод Run, который получает путь к файлу конфигурации. Для задания текстур можно использовать два варианта:
Методом void SetTextures(const pTTextureListManager &Val); задать менеджер текстур, из которого в последствии будут выбраны нужные текстуры по параметрам, указанным в конфигурации.
При помощи  void AddTexturePointer(const pTTexture &Val); сформировать группу текстур, которые будут переданы в загружаемый шрифт.
Для Конфигурации текстурного шрифта используется обычный текстовый файл с заданной структурой. Он выглядит так:
# - комментарий
info name="font_name" size="16"

texture list="test" name="tex1"
texture list="test" name="tex2"

cell char=" " textureid="0" x="0"  y="0" width="16" height="16" shiftx="1" shifty="1" cursorshift="14"
cell char="A" textureid="1" x="16" y="0" width="16" height="16" shiftx="2" shifty="1" cursorshift="15"
Имеет три блока — info, texture, cell.
Блок info — информация о шрифте, его имя и размер. Размер можно указывать любой, он не используется в текстурных шрифтах.
Блок texture — указывает, какую текстуру использовать из менеджера текстур, её список и её имя. Если у TBitmapFontLoader не задан менеджер текстур, то эти блоки в конфигурации игнорируются.
Блок cell — описание одного глифа, нужно задавать символ, индекс текстуры, позицию текстуры и размер части текстуры с символом, смешение символа при выводе в строке и смещение курсора строки.
В общих чертах текстурные шрифты рассмотрены. Далее речь пойдёт о динамической генерации шрифтов при помощи библиотеки freetype.

Генерируемые шрифты.

Второй вариант использования шрифтов — генерировать текстуры глифов при помощи библиотеки FreeType. Для этого используется внешний модуль движка FreeTypeFontSystem, доступный в расширениях. Так как код модуля открыт, всегда можно добавить свой функционал или сделать аналогичную систему на базе другой прикладной библиотеки.

Для начала работы с FreeTypeFontSystem нужно добавить код из модуля в проект, прописать пути к исполняемым файлам. Код находится в папке FreeTypeFontSystem, которая в свою очередь входит в K5EngineExternSystems. Так же нужно подключить библиотеку libfreetype.dll, которую можно скачать и собрать с официального сайта проекта http://www.freetype.org или взять уже собранную из UserLibs движка. Далее в классе приложения объявляется TFTDevice и выполняется его старт после инициализации  основного устройства. По завершении работы программы при разрушении  TFTDevice автоматически вызовет метод Stop(), однако его можно вызвать и в ручную при остановке приложения. На этом инициализация системы закончена. Так же есть один нюанс, всё же работа FreeTypeFontSystem зависит от типа используемого системы вывода графики. Для TFTDevice  нужно указывать, для какого рендера генерировать глифы. Это делается при помощи метода SetGenTextureType, который принимает два значения: EN_FTGTT_GL или EN_FTGTT_DX8. По умолчанию используется EN_FTGTT_GL. То есть если вы используете TWinApiDX8Device, то для TFTDevice нужно вызвать метод SetGenTextureType с флагом  EN_FTGTT_DX8.

Далее нужно загрузить исходный  шрифт, из которого в дальнейшем будут генерироваться текстуры букв. Класс такого шрифта — TFTFontFace, соответственно для них есть список TFTFontFaceList. Загрузка  TFTFontFace производится при помощи класса TFTFontFaceLoader. Для этого используется метод pTFTFontFace Run(const wstring &FPath, const wstring &FName=L""), в который передаётся путь к шрифту формата ttf или другого поддерживаемого FreeType и не обязательное имя исходного шрифта. В результате мы получим указатель на TFTFontFace, который желательно поместить в список TFTFontFaceList. В противном случае надо не забыть потом в ручную отчистить данные. Код загрузки исходного шрифта будет выглядеть примерно так:

  TFolderWorker FolderWorker; 
  wstring AppPath(FolderWorker.GetAppPath()); 

  TFTFontFaceLoader FontLoader;
  pTFTFontFace Arial = FontLoader.Run(AppPath + Fonts\\arial.ttf", L"arial");
  FontFaces.Add(Arial);
В данном коде объявили экземпляр класса TFolderWorker и получили путь к каталогу приложения AppPath. Объявили загрузчик FontLoader для базового шрифта, загрузили  Arial по указанному пути и добавили его в FontFaces (экземпляр  TFTFontFaceList). Далее можно приступать к непосредственному созданию шрифтов FreeType. Для этого понадобится класс  TFTFont. Рассмотрим его описание:
class TFTFont:public TBaseFont
{
  protected:
    pTFTFontFace FontFace;
    pTBaseDevice Device;
  public:
    TFTFont();
    TFTFont(const pTFTFontFace &NewFontFace, const pTBaseDevice  &DeviceVal,
        const int &NewFontSize = 16, const wstring &FontName=L"");

    virtual ~TFTFont();

    void Set(const pTFTFontFace &NewFontFace, const pTBaseDevice  &DeviceVal,
        const int &NewFontSize = 16, const wstring &FontName=L"");

    pTGlyph GetGlyph(const wchar_t &Symbol);
};
Как видно, при создании или инициализации методом Set класс получает указатель на  TFTFontFace, указатель на устройство, размер шрифта и собственное имя. Метод GetGlyph переопределён из базового класса TBaseFont, возвращает указатель на глиф по заданному символу. Как упоминалось ранее, код класса открыт, поэтому можно детально посмотреть, как всё работает. Пример создания шрифтов:
  Fonts.Add( new TFTFont(Arial, &Device, 10, L"arial10") );
  Fonts.Add( new TFTFont(Arial, &Device, 12, L"arial12") );
  Fonts.Add( new TFTFont(Arial, &Device, 14, L"arial14") );
Переменная  Fonts это экземпляр класса TFontList, Мы при помощи метода Add добавляем динамически созданный экземпляр класса TFTFont, конструктор которого получает указатель на ранее загруженный  TFTFontFace  Arial, устройство, размер и имя шрифта. Далее уже шрифт будет взят из списка и передан целевому классу текста. Так как TFTFont  и TBitmapFont  наследуются от общего базового класса, то не имеет значения, какой именно класс шрифта используется текстом. Так же вы можете создать собстенный класс шрифта, главное соблюдать общий интерфейс. Далее приступим к разбору текстового объекта.

Текстовый объект.

Рассмотрим текстовый объект, его заголовочный файл находится по пути [K5Engine][Core][GraphicSystem][Text][Text.h]:

class TText: public TBaseGraphicObject
{
  protected:
    wstring Text;
    pTBaseFont  Font;

    pTBaseViewMatrixWorker ViewMatrix;

    TSpriteList  Line;

    enAlignment  Alignment;

    float TextWidth;
    float TextHeight;

    TColor OldColor;
    TPoint RealDrawPos;
  public:
    TPoint Scale;
    TColor Color;
  protected:
    inline void GenLine(const wstring &TextLine);
    inline void InitData();

    void ToSetDevice();
    void ToDraw();
  public:
    TText();
    TText(const TText &Val);
    virtual ~TText();

    void operator=(const TText &Val);

    void SetFont(const pTBaseFont &Val);
    pTBaseFont GetFont() const;

    void Set(const wstring &Val);
    void Set(const int &Val);
    void Set(const float &Val);
    wstring Get() const;

    void Add(const wchar_t &Val);
    void Add(const wstring &Val);

    void Del();

    int GetFontSize() const;

    float GetWidth()  const;
    float GetHeight() const;

    void SetAlignment(const enAlignment &Value);
    enAlignment GetAlignment() const;

    pTSpriteList GetLinePtr();
    int GetStrLength() const;
};
Сначала разберём защищённые элементы класса, нужные в работе:
Text – текст, который выводится, задаётся набором методов Set, которые могут принимать текст в виде wstring строки, float и int переменные. Получить текст можно при помощи метода Get. Так же при помощи методов Add можно добавлять к уже заданному тексту символ или строку. Для удаления последнего символа из строки используется метод Del.
Font — указатель на шрифт, который будет использоваться при выводе текста. Задаётся и возвращается при помощи методов  SetFont и  GetFont.
ViewMatrix — матрица отображения объекта, которая зависит от контекста рисования. При помощи неё изменяется масштаб, позиция и наклон текста. Создаётся при задании устройства классу текста.
Line — список спрайтов-букв. К нему можно получить доступ и применить эффекты к каждому из символов. Имя спрайтов задаётся как отображаемый символ из строки. Спрайты генерируются каждый раз, когда задаётся или изменяется текстовая строка. Для получения указателя на список спрайтов-букв используется метод  GetLinePtr. Для того, что бы узнать длину строки — метод  GetStrLength.
Alignment — выравнивание текста, может принимать значения EN_A_Center, EN_A_Left и EN_A_Right. По умолчанию выравнивание задано  EN_A_Center. Для задания и получения значения выравнивания используются методы  SetAlignment и  GetAlignment.
TextWidth и TextHeight — ширина и высота текстовой строки, параметры доступны только для считывания. Для этого применяются методы  GetWidth и  GetHeight.

Так же у текстового объекта есть два открытых параметра, это его масштаб -  Scale и его цвет Color. Масштаб по умолчанию задан в 1 по x и y координатам, цвет по умолчанию белый (значения компонент цвета равны 1). Так же не стоит забывать, что TText является наследником от TBaseGraphicObject, соответственно имеет и его параметры, такие как позиция, угол, центр, флаг видимости и имя. Экземпляры TText можно присваивать друг другу, для этого переопределён оператор присваивания. Аналогично спрайтам, для текстовых объектов есть свои списки TTextList и менеджеры TTextListManager. Заголовочные файлы, относящиеся к тексту, находятся по пути [K5Engine][Core][GraphicSystem][Text].

Итог.

В этой статье рассмотрены основные аспекты работы с текстом в движке K5Engine. Дано описание объектов шрифтов двух типов, примеры их инициализации.
Так же разобран класс текста. В уроке, который прилагается к данной статье, даны примеры использования обоих видов шрифтов и манипулирование с текстовыми объектами.
Уроки можно скачать по этой ссылке: http://vector-k5.com.ua/download/K5Engine3/tutorials.

5 февраля 2012