2.9.16
Coherent GT
A modern user interface library for games
Bitmap Fonts

By default Coherent GT is using FreeType for rendering fonts and manually reads .ttf / .otf files. Coherent GT can also work with Bitmap Font with bitmaps supplied by the client.

To use bitmap fonts, call Coherent::UIGT::UISystem::AddBitmapFont providing a thorough description of the font and bitmaps.

This method has 2 overloads:

  • With encoded image data.
UISystem->AddBitmapFont(imageFilesData, imageFileSizes, imagesCount, bitmapFontDescription)
Warning
The image file decoding is synchronous in the same callstack.
  • With preloaded images which Coherent GT can directly use.
UISystem->AddBitmapFont(preloadedImagesData, imagesCount, bitmapFontDescription)
Note
The bitmap images must be 1 channel Alpha textures.

On frontend side, using bitmap fonts doesn't differ much from what be otherwise required - just tell GT to use the font family you just loaded:

body {
font-family: "MyBitmapFontName";
}

Example usage

One of the most popular tools for generating bitmap fonts is BMFont by AngelCode. Here is an example how to parse a binary .fnt file and load the font in Coherent GT.

// BMFont Parsing
#pragma pack(push, 1)
struct BMFontBlock
{
    uint8_t Type;
    uint32_t Size;
};

struct BMFontBlockInfo
{
    int16_t FontSize;
    uint8_t BitField; //bit 0: smooth, bit 1 : unicode, bit 2 : italic, bit 3 : bold, bit 4 : fixedHeigth, bits 5 - 7 : reserved
    uint8_t CharSet;
    uint16_t StretchH;
    uint8_t AA;
    uint8_t PaddingUp;
    uint8_t PaddingRight;
    uint8_t PaddingDown;
    uint8_t PaddingLeft;
    uint8_t SpacingHoriz;
    uint8_t SpacingVert;
    uint8_t Outline;
    // char [n + 1] FontName. Null terminated string
};

struct BMFontBlockCommon
{
    uint16_t LineHeight;
    uint16_t BaseLine;
    uint16_t ScaleW;
    uint16_t ScaleH;
    uint16_t Pages;
    uint8_t BitField; // bits 0-6: reserved, bit 7: packed
    uint8_t AlphaChannel;
    uint8_t RedChannel;
    uint8_t GreenChannel;
    uint8_t BlueChannel;
};

// BMFontBlockPages
// PageNames p * (n + 1), p-pages,each with n-length

// Repeated until all chars are described
// NumberOfChars = BMFontBlock.Size / 20
struct BMFontBlockChars
{
    uint32_t Id;
    uint16_t X;
    uint16_t Y;
    uint16_t Width;
    uint16_t Height;
    int16_t XOffset;
    int16_t YOffset;
    int16_t Advance;
    uint8_t Page;
    uint8_t Channel;
};

struct BMFontBlockKerningPairs
{
    uint32_t First;
    uint32_t Second;
    int16_t Amount;
};
#pragma pack(pop)
struct BMFontData
{
    std::string Name;
    unsigned Size;
    bool IsBold;
    bool IsItalic;
    unsigned Baseline;
    unsigned LineHeight;
    unsigned TextureWidth;
    unsigned TextureHeight;
    struct CharInfo
    {
        unsigned Id;
        unsigned X;
        unsigned Y;
        unsigned Width;
        unsigned Height;
        int OffsetX;
        int OffsetY;
        unsigned Advance;
        unsigned ImageIndex;
    };
    std::vector<CharInfo> Chars;
    std::vector<std::string> ImageNames;
    struct KerningInfo
    {
        unsigned FirstId;
        unsigned SecondId;
        int Amount;
    };
    std::vector<KerningInfo> Kerning;
};

bool ParseBMFontFile(const char* filename, BMFontData& data)
{
    std::ifstream file(filename, std::ios::binary);
    if (!file)
    {
        return false;
    }

    uint32_t magicBytes;
    file.read(reinterpret_cast<char*>(&magicBytes), 4);
    if (magicBytes != 0x03464d42) // BMF3   BMF is magic signature and 3 is the current version
    {
        return false;
    }

    // Assume there is one from each type of blocks

    // Info block
    BMFontBlock infoBlockSize;
    file.read(reinterpret_cast<char*>(&infoBlockSize), sizeof(BMFontBlock));
    if (infoBlockSize.Type != 1) // Check if it a info block
    {
        return false;
    }
    std::unique_ptr<char[]> infoBlockData = std::make_unique<char[]>(infoBlockSize.Size);
    file.read(infoBlockData.get(), infoBlockSize.Size);
    BMFontBlockInfo* infoBlock = reinterpret_cast<BMFontBlockInfo*>(infoBlockData.get());

    data.Name = std::string(infoBlockData.get() + sizeof(BMFontBlockInfo));
    data.Size = std::abs(infoBlock->FontSize);
    data.IsBold = !!(infoBlock->BitField & (1 << 4));
    data.IsItalic = !!(infoBlock->BitField & (1 << 5));

    // Common block
    BMFontBlock commonBlockSize;
    file.read(reinterpret_cast<char*>(&commonBlockSize), sizeof(BMFontBlock));
    if (commonBlockSize.Type != 2
        || commonBlockSize.Size != sizeof(BMFontBlockCommon))
    {
        return false;
    }
    BMFontBlockCommon commonBlock;
    file.read(reinterpret_cast<char*>(&commonBlock), sizeof(BMFontBlockCommon));

    data.LineHeight = commonBlock.LineHeight;
    data.Baseline = commonBlock.BaseLine;
    data.TextureWidth = commonBlock.ScaleW;
    data.TextureHeight = commonBlock.ScaleH;

    // Pages block
    BMFontBlock pagesBlockSize;
    file.read(reinterpret_cast<char*>(&pagesBlockSize), sizeof(BMFontBlock));
    if (pagesBlockSize.Type != 3)
    {
        return false;
    }

    // Pages are commonBlock.Pages * (n + 1) bytes long. Each page is n byte string with 1 extra byte for terminating null
    const auto pageStringLength = (pagesBlockSize.Size / commonBlock.Pages) - 1;
    std::unique_ptr<char[]> pagesBlockData = std::make_unique<char[]>(pagesBlockSize.Size);
    file.read(pagesBlockData.get(), pagesBlockSize.Size);

    data.ImageNames.reserve(commonBlock.Pages);
    for (auto i = 0; i < commonBlock.Pages; ++i)
    {
        data.ImageNames.push_back(pagesBlockData.get() + i * pageStringLength);
    }

    // Chars block
    BMFontBlock charsBlockSize;
    file.read(reinterpret_cast<char*>(&charsBlockSize), sizeof(BMFontBlock));

    if (charsBlockSize.Type != 4)
    {
        return false;
    }

    const auto numChars = charsBlockSize.Size / sizeof(BMFontBlockChars);
    std::unique_ptr<BMFontBlockChars[]> charsBlockData = std::make_unique<BMFontBlockChars[]>(numChars);
    file.read(reinterpret_cast<char*>(charsBlockData.get()), charsBlockSize.Size);

    data.Chars.reserve(numChars);
    for (auto i = 0; i < numChars; ++i)
    {
        if (charsBlockData[i].Channel != 8 // Alpha Channel
            && charsBlockData[i].Channel != 15) // All Channels
        {
            return false;
        }

        BMFontData::CharInfo chInfo;
        chInfo.Id = charsBlockData[i].Id;
        chInfo.ImageIndex = charsBlockData[i].Page;
        chInfo.X = charsBlockData[i].X;
        chInfo.Y = charsBlockData[i].Y;
        chInfo.Width = charsBlockData[i].Width;
        chInfo.Height = charsBlockData[i].Height;
        chInfo.OffsetX = charsBlockData[i].XOffset;
        chInfo.OffsetY = charsBlockData[i].YOffset;
        chInfo.Advance = charsBlockData[i].Advance;

        data.Chars.push_back(chInfo);
    }

    // Kerning block
    BMFontBlock kerningBlockSize;
    file.read(reinterpret_cast<char*>(&kerningBlockSize), sizeof(BMFontBlock));
    if (file)
    {
        if (kerningBlockSize.Type != 5)
        {
            return false;
        }

        const auto numKerning = kerningBlockSize.Size / sizeof(BMFontBlockKerningPairs);
        std::unique_ptr<BMFontBlockKerningPairs[]> kerningBlockData = std::make_unique<BMFontBlockKerningPairs[]>(numKerning);
        file.read((char*)kerningBlockData.get(), kerningBlockSize.Size);

        data.Kerning.reserve(numKerning);
        for (auto i = 0; i < numKerning; ++i)
        {
            BMFontData::KerningInfo kInfo;
            kInfo.FirstId = kerningBlockData[i].First;
            kInfo.SecondId = kerningBlockData[i].Second;
            kInfo.Amount = kerningBlockData[i].Amount;

            data.Kerning.push_back(kInfo);
        }
    }

    return true;
}

After the .fnt file is parsed we need to initialize Coherent::UIGT::BitmapFontDescription struct with the required data.

// Load Bitmap Font
BMFontData data;
if (ParseBMFontFile("BitmapFont.fnt", data))
{
    std::vector<std::vector<char>> images;
    std::vector<const char*> imagesData;
    std::vector<unsigned> imagesSize;
    for (auto i = 0; i < data.ImageNames.size(); ++i)
    {
        std::ifstream file(data.ImageNames[i], std::ios::binary);
        if (!file)
        {
            return false;
        }

        std::vector<char> fileContents((std::istreambuf_iterator<char>(file)),
            std::istreambuf_iterator<char>());
        images.push_back(std::move(fileContents));

        imagesData.push_back(images.back().data());
        imagesSize.push_back(unsigned(images.back().size()));
    }

    Coherent::UIGT::BitmapFontDescription desc;
    desc.FontFamily = data.Name.c_str();
    desc.Size = data.Size;
    desc.IsBold = data.IsBold;
    desc.IsItalic = data.IsItalic;
    desc.Baseline = data.Baseline;
    desc.LineHeight = data.LineHeight;

    std::vector<Coherent::UIGT::BitmapFontDescription::CharInfo> descChars;
    descChars.reserve(data.Chars.size());
    for (const auto& ch : data.Chars)
    {
        Coherent::UIGT::BitmapFontDescription::CharInfo info;
        info.CharCode = ch.Id;
        info.X = float(ch.X) / float(data.TextureWidth);
        info.Y = float(ch.Y) / float(data.TextureWidth);
        info.Width = float(ch.Width) / float(data.TextureWidth);
        info.Height = float(ch.Height) / float(data.TextureWidth);
        info.OffsetX = ch.OffsetX;
        info.OffsetY = ch.OffsetY;
        info.Advance = ch.Advance;
        info.ImageIndex = ch.ImageIndex;
        descChars.push_back(info);
    }
    desc.Chars = descChars.data();
    desc.NumChars = unsigned(descChars.size());

    std::vector<Coherent::UIGT::BitmapFontDescription::KerningPairInfo> descKerning;
    descKerning.reserve(data.Kerning.size());
    for (const auto& ker : data.Kerning)
    {
        Coherent::UIGT::BitmapFontDescription::KerningPairInfo info;
        info.FirstCharCode = ker.FirstId;
        info.SecondCharCode = ker.SecondId;
        info.Amount = ker.Amount;
        descKerning.push_back(info);
    }

    desc.KerningPairs = descKerning.data();
    desc.NumKerningPairs = unsigned(descKerning.size());

    m_UISystem->AddBitmapFont(imagesData.data(), imagesSize.data(), unsigned(images.size()), desc);
}

How to generate bitmap font with BMFont

  • In Font Settings make sure you are generating Unicode char codes. Also make sure that Match char height is checked. Without this the char size is not in pixels and Coherent GT will not layout it correctly.
bmfont-font-settings.png
Font Settings
  • In Export Options make sure the textures are 8 bit and that the glyph is outputed in the alpha channel.
bmfont-export-settings.png
Export Settings