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:
UISystem->AddBitmapFont(imageFilesData, imageFileSizes, imagesCount, bitmapFontDescription)
UISystem->AddBitmapFont(preloadedImagesData, imagesCount, bitmapFontDescription)
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";}
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);
}