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); }