Skip to content

Instantly share code, notes, and snippets.

@Flix01
Last active March 1, 2024 10:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Flix01/13374fabd73214f82362 to your computer and use it in GitHub Desktop.
Save Flix01/13374fabd73214f82362 to your computer and use it in GitHub Desktop.
imguifontloader: imgui extension that loads txt-based .fnt and .ttf files
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
#include "imguifontloader.h"
#ifdef IMGUIFONTLOADER_USES_STB_TRUETYPE
# define STB_TRUETYPE_IMPLEMENTATION
# include <stb_truetype.h>
#endif //IMGUIFONTLOADER_USES_STB_TRUETYPE
#include <stdio.h>
#include <string.h>
#include <new>
#ifndef MAX_FILENAME_SIZE
# define MAX_FILENAME_SIZE 1024
#endif //MAX_FILENAME_SIZE
struct FntBlob {
ImFont::FntInfo info;
ImFont::FntCommon common;
ImVector<ImFont::FntGlyph> glyphs;
ImVector<ImFont::FntKerning> kernings;
ImVector< char[MAX_FILENAME_SIZE] > filenames;
void Clear() {
// These should free the memory AFAIK
ImVector<ImFont::FntGlyph> tmp1;glyphs.swap(tmp1);
ImVector<ImFont::FntKerning> tmp2;kernings.swap(tmp2);
ImVector< char[MAX_FILENAME_SIZE] > tmp3;filenames.swap(tmp3);
}
~FntBlob() {Clear();}
};
ImFontEx::ImFontEx() : ImFont(),dataIsNotBinFnt(false) {}
void ImFontEx::Clear() {
if (dataIsNotBinFnt && DataOwned && Data && DataSize>0) {
FntBlob* fntBlob = (FntBlob*) Data;
fntBlob->Clear();
fntBlob->glyphs.~ImVector<ImFont::FntGlyph>();
fntBlob->kernings.~ImVector<ImFont::FntKerning>();
fntBlob->filenames.~ImVector<char[MAX_FILENAME_SIZE]>();
// The rest should be done by ImFont::Clear()
}
ImFont::Clear();
dataIsNotBinFnt = false;
}
// Just a refactored method here:
bool ImFontEx::ConvertFileToMemory(const char* filename) {
IM_ASSERT(!IsLoaded()); // Call Clear()
// Load file
FILE* f;
if ((f = fopen(filename, "rb")) == NULL)
return false;
if (fseek(f, 0, SEEK_END))
{
fclose(f);
return false;
}
const long f_size = ftell(f);
if (f_size == -1)
{
fclose(f);
return false;
}
DataSize = (size_t)f_size;
if (fseek(f, 0, SEEK_SET))
{
fclose(f);
return false;
}
if ((Data = (unsigned char*)ImGui::MemAlloc(DataSize)) == NULL)
{
fclose(f);
return false;
}
if (fread(Data, 1, DataSize, f) != DataSize)
{
fclose(f);
ImGui::MemFree(Data);
return false;
}
fclose(f);
DataOwned = true;
return true;
}
// Same as ImFont::~ImFont(). Nedded because otherwise it will call ImFont::Clear(), instead of ImFontEx::Clear() [No polymorphism]
ImFontEx::~ImFontEx() {Clear();}
// Same as ImFont::LoadFromFile(...). Needed because otherwise it will call ImFont::LoadFromMemory(...) at the end, instead of ImFontEx::LoadFromMemory(...) [No polymorphism]
bool ImFontEx::LoadFromFile(const char* filename) {
bool rv = ConvertFileToMemory(filename);
return rv ? LoadFromMemory(Data, DataSize) : false;
}
bool ImFontEx::LoadFromMemory(const void* data, size_t data_size) {
const bool isTextFontFile = (data_size>=5 && strncmp((const char*) data,"info ",5)==0);// text .fnt files start with "info " AFAIK
return isTextFontFile ? LoadTextFntFileFromMemory(data,data_size) : ImFont::LoadFromMemory(data,data_size);
}
bool ImFontEx::LoadTextFntFileFromMemory(const void* data, size_t data_size) {
ImVector<char> textFileInMemory;
if (DataOwned) {
// Problem: Now we have the text file in the place where the binary file should be
// we should put it somewhere else:
textFileInMemory.resize(data_size+1);
memcpy((void*) &textFileInMemory[0],data,data_size);
textFileInMemory[data_size]='\0';
Clear();DataSize=0;
data = (const void*) &textFileInMemory[0];
}
const char* pdata = (const char*) data;
// From now on we must use "pdata" and "data_size" only
// We must allocate a binary structure calculated from "pdata" and "data_size"
DataSize = sizeof(FntBlob);
if ((Data = (unsigned char*)ImGui::MemAlloc(DataSize)) == NULL) return false;
DataOwned = true; // Note: from now on whenever we "return false", we should call "Clear()" before.
FntBlob* pf = (FntBlob*) Data;
new(pf) FntBlob();
FntBlob& f = *pf;
// Here we must fill all the fields of "f" based on "pdata" and "data_size":
char tmp[1024];tmp[0]='\0';
int tmpi;size_t gsize;
const char* buf_end = pdata + data_size;
//# define DEBUG_PARSED_DATA
for (const char* line_start = pdata; line_start < buf_end; ) {
const char* line_end = line_start;
while (line_end < buf_end && *line_end != '\n' && *line_end != '\r') line_end++;
if (strncmp(line_start,"char ",4)==0 && GlyphsCount>0) {
gsize = f.glyphs.size();
f.glyphs.resize(gsize+1);
ImFont::FntGlyph& g = f.glyphs[gsize];
//char id=32 x=236 y=116 width=3 height=1 xoffset=-1 yoffset=15 xadvance=4 page=0 chnl=15
int a[10] = {0,0,0,0,0,0,0,0,0,0};
if (sscanf(&line_start[4], " id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i page=%i chnl=%i",
&a[0],&a[1],&a[2],&a[3],&a[4],&a[5],&a[6],&a[7],&a[8],&a[9]))
{
g.Id = (unsigned int) a[0];
g.X = (unsigned short) a[1];
g.Y = (unsigned short) a[2];
g.Width = (unsigned short) a[3];
g.Height = (unsigned short) a[4];
g.XOffset = (signed short) a[5];
g.YOffset = (signed short) a[6];
g.XAdvance = (signed short) a[7];
g.Page = (unsigned char) a[8];
g.Channel = (unsigned char) a[9];
# ifdef DEBUG_PARSED_DATA
fprintf(stderr,"%d)\tId=%d X=%d Y=%d Width=%d Height=%d XOffset=%d YOffset=%d XAdvance=%d Page=%d Channel=%d\n",
(int)gsize,(int) g.Id,(int)g.X,(int)g.Y,(int)g.Width,(int)g.Height,
(int)g.XOffset,(int)g.YOffset,(int)g.XAdvance,(int)g.Page,(int)g.Channel);
# endif //DEBUG_PARSED_DATA
}
else fprintf(stderr,"Warning in ImFontEx::LoadTextFntFileFromMemory(\"glyph \"): skipped line [%s.50] (parsing error).\n",line_start);
}
else if (strncmp(line_start,"info ",5)==0) {
ImFont::FntInfo& info = f.info;
tmp[0]='\0';
char tmp2[1024];tmp2[0]='\0';
int a[14] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0};
const char* pline = NULL;
//We must skip the font name first (we store it inside tmp)
static const int quote = (int) '"';
const char* q1=NULL;const char* q2=NULL;
q1 = strchr((const char*) &line_start[5], quote ); // This method is better than sscanf, because the string can contain spaces
if (q1) {
const char* q11 = ++q1;
q2 = strchr(q11, quote);
if (q2) {
const size_t sz = q2-q11;
strncpy(tmp,q11,sz);
tmp[sz]='\0';
pline = ++q2;
}
}
if (pline && sscanf(pline, " size=%i bold=%i italic=%i charset=%s unicode=%i stretchH=%i smooth=%i aa=%i padding=%i,%i,%i,%i spacing=%i,%i outline=%i",
&a[0],&a[1],&a[2],tmp2,&a[3],&a[4],&a[5],&a[6],&a[7],&a[8],&a[9],&a[10],&a[11],&a[12],&a[13]))
{
// This is necessary because often tmp2="", with comma included:
size_t tmp2Size;
while ((tmp2Size=strlen(tmp2))>0 && tmp2[tmp2Size-1]=='"') tmp2[tmp2Size-1]='\0';
while ((tmp2Size=strlen(tmp2))>0 && tmp2[0]=='"') {static char tmp22[1024];strcpy(tmp22,&tmp2[1]);strcpy(tmp2,tmp22);}
info.FontSize = (signed short) a[0];
info.BitField = (a[5]==1) | ((a[3]==1)<<1) | ((a[2]==1)<<2) | ((a[1]==1)<<3);// | ((FixedHeight==1)<<4); // not sure this is correct (TO CHECK)
info.CharSet = strlen(tmp2)==0 ? 0 : tmp2[0];
info.StretchH = (unsigned short) a[4];
info.AA = (unsigned char) a[6];
info.PaddingUp = (unsigned char) a[7];
info.PaddingRight = (unsigned char) a[8];
info.PaddingDown = (unsigned char) a[9];
info.PaddingLeft = (unsigned char) a[10];
info.SpacingHoriz = (unsigned char) a[11];
info.SpacingVert = (unsigned char) a[12];
info.Outline = (unsigned char) a[13];
# ifdef DEBUG_PARSED_DATA
fprintf(stderr,"info:\nFontFace=%s\nFontSize=%d\nBitField=%d\nCharSet=%d\nStretchH=%d\nAA=%d\nPadding=%d,%d,%d,%d\nSpacing=%d,%d\nOutline=%d\n",
tmp,(int) info.FontSize,(int)info.BitField,(int)info.CharSet,(int)info.StretchH,(int)info.AA,
(int)info.PaddingUp,(int)info.PaddingRight,(int)info.PaddingDown,(int)info.PaddingLeft,(int)info.SpacingHoriz,
(int)info.SpacingVert,(int)info.Outline);
# endif //DEBUG_PARSED_DATA
}
else fprintf(stderr,"Warning in ImFontEx::LoadTextFntFileFromMemory(\"info \"): skipped line [%s.50] (parsing error).\n",line_start);
}
else if (strncmp(line_start,"common ",7)==0) {
ImFont::FntCommon& c = f.common;
int a[10] = {0,0,0,0,0,0,0,0,0,0};
// common lineHeight=16 base=13 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0
if (sscanf(&line_start[7], " lineHeight=%i base=%i scaleW=%i scaleH=%i pages=%i packed=%i alphaChnl=%i redChnl=%i greenChnl=%i blueChnl=%i",
&a[0],&a[1],&a[2],&a[3],&a[4],&a[5],&a[6],&a[7],&a[8],&a[9]))
{
c.LineHeight = (unsigned short) a[0];
c.Base = (unsigned short) a[1];
c.ScaleW = (unsigned short) a[2];
c.ScaleH = (unsigned short) a[3];
c.Pages = (unsigned short) a[4];
c.BitField = (unsigned char) a[5];
c.Channels[0] = (unsigned char) a[6];
c.Channels[1] = (unsigned char) a[7];
c.Channels[2] = (unsigned char) a[8];
c.Channels[3] = (unsigned char) a[9];
#ifdef DEBUG_PARSED_DATA
fprintf(stderr,"common:\nLineHeight=%d\nBase=%d\nScaleW=%d\nScaleH=%d\nPages=%d\nBitField=%d\nChannels=%d,%d,%d,%d\n",
(int) c.LineHeight,(int)c.Base,(int)c.ScaleW,(int)c.ScaleH,(int)c.Pages,
(int)c.BitField,(int)c.Channels[0],(int)c.Channels[1],(int)c.Channels[2],(int)c.Channels[3]);
#endif //DEBUG_PARSED_DATA
}
else fprintf(stderr,"Warning in ImFontEx::LoadTextFntFileFromMemory(\"common \"): skipped line [%s.50] (parsing error).\n",line_start);
}
else if (strncmp(line_start,"page id=0 ",10)==0) {
// well, we just support one page, but maybe this is what "filenames" refer to
// we just fill the first filename we found in the line for now (I must see a file with more than one page to parse it correctly...):
if (f.filenames.size()==0) {
tmp[0]='\0';
static const int quote = (int) '"';
const char* q1=NULL;const char* q2=NULL;
q1 = strchr((const char*) &line_start[10], quote );
if (q1) {
const char* q11 = ++q1;
q2 = strchr(q11, quote);
if (q2) {
const size_t sz = q2-q11;
strncpy(tmp,q11,sz);
tmp[sz]='\0';
}
}
if (strlen(tmp)>0) {
size_t tmpSize;
while ( (tmpSize=strlen(tmp))>0 && tmp[tmpSize-1]=='\"') tmp[tmpSize-1]='\0';
gsize = f.filenames.size();
f.filenames.resize(gsize+1);
char* c = &f.filenames[gsize][0];
c[0]='\0';
if (strlen(tmp)<=MAX_FILENAME_SIZE) {
strcpy(c,tmp);
# ifdef DEBUG_PARSED_DATA
fprintf(stderr,"Filename = %s\n",c);
# endif //DEBUG_PARSED_DATA
}
}
else fprintf(stderr,"Warning in ImFontEx::LoadTextFntFileFromMemory(\"page \"): skipped line [%s.50] (parsing error).\n",line_start);
}
}
else if (strncmp(line_start,"chars count=",12)==0) {
if (sscanf(line_start, "chars count=%i", &tmpi)) {
GlyphsCount = tmpi;
f.glyphs.reserve(GlyphsCount);
# ifdef DEBUG_PARSED_DATA
fprintf(stderr,"GlyphsCount = %d\n",(int)GlyphsCount);
# endif //DEBUG_PARSED_DATA
}
else fprintf(stderr,"Warning in ImFontEx::LoadTextFntFileFromMemory(): skipped line [%s.50] (parsing error).\n",line_start);
}
line_start = line_end+1;
}
# undef DEBUG_PARSED_DATA
// Map f to struct fields:
Info = &f.info;Common = &f.common;
GlyphsCount=f.glyphs.size();Glyphs = GlyphsCount>0 ? &f.glyphs[0] : NULL;
KerningCount=f.kernings.size();Kerning = KerningCount>0 ? &f.kernings[0] : NULL;
Filenames.resize(f.filenames.size());for (size_t i=0;i<f.filenames.size();i++) Filenames[i] = &f.filenames[i][0];
dataIsNotBinFnt = true;
BuildLookupTable();
return true;
}
#ifdef IMGUIFONTLOADER_USES_STB_TRUETYPE
// Based on stbtt_BakeFontBitmap(...):
static int stbtt_BakeFontBitmap2(const stbtt_fontinfo& f,
float scale, // the scale we must apply to the font
unsigned char *pixels, int pw, int ph, // bitmap to be filled in
const ImVector<int>& glyphs, // characters to bake
ImVector<stbtt_bakedchar>& charDataOut, // size is the size of valid glyphs (<=glyphs.size())
ImVector<bool>& invalidGlyphsOut // same size as glyphs
)
{
int x,y,bottom_y, i;
STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels
x=y=1;
bottom_y = 1;
const int num_chars = (int) glyphs.size();
invalidGlyphsOut.resize(num_chars);
charDataOut.reserve(num_chars);size_t charDataOutSize;
int advance, lsb, x0,y0,x1,y1,gw,gh,g;
for (i=0; i < num_chars; ++i) {
const int& glyph = glyphs[i];
bool& glyphInvalid = invalidGlyphsOut[i];
g = stbtt_FindGlyphIndex(&f, glyph);
if (g==0) {
// skipping
fprintf(stderr,"Warning: glyph number %d/%d (codepoint 0x%0x) does not exist in this ttf. Skipping it.\n",i+1,(int)glyphs.size(),glyph);
glyphInvalid=true; // it means it's not present: this way we can rebuild the correspondence between "glyphs" and "charDataOut"
continue;
}
glyphInvalid = false;
stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb);
stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1);
gw = x1-x0;
gh = y1-y0;
if (x + gw + 1 >= pw)
y = bottom_y, x = 1; // advance to next row
if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row
return -i;
STBTT_assert(x+gw < pw);
STBTT_assert(y+gh < ph);
stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g);
charDataOutSize = charDataOut.size();
charDataOut.resize(charDataOutSize+1);
stbtt_bakedchar& cd = charDataOut[charDataOutSize];
//--
cd.x0 = (stbtt_int16) x;
cd.y0 = (stbtt_int16) y;
cd.x1 = (stbtt_int16) (x + gw);
cd.y1 = (stbtt_int16) (y + gh);
cd.xadvance = scale * advance;
cd.xoff = (float) x0;
cd.yoff = (float) y0;
x = x + gw + 1;
if (y+gh+1 > bottom_y)
bottom_y = y+gh+1;
}
return bottom_y;
}
bool ImFontEx::LoadFromFileTTF(const char* filename,float fontSizeInPixels,int& bitmapW,int& bitmapH,int& bitmapN,ImVector<unsigned char>& bitmapOut,bool allowNonSquaredBitmap,const ImVector<int>& glyphs) {
bool rv = ConvertFileToMemory(filename);
return rv ? LoadFromMemoryTTF(Data, DataSize,fontSizeInPixels,bitmapW,bitmapH,bitmapN,bitmapOut,allowNonSquaredBitmap,glyphs) : false;
}
const ImVector<int> &ImFontEx::GetDefaultGlyphs() {
static ImVector<int> glyphs;
if (glyphs.size()==0) {
glyphs.reserve(255);
for (int i=32;i<127;i++) glyphs.push_back(i);
for (int i=161;i<256;i++) glyphs.push_back(i);
glyphs.push_back(0x20AC); //€ (euro glyph)
//glyphs.push_back(0xCF14); //to remove (some japanese? glyph to test what happens if a glyph is not present...)
}
return glyphs;
}
bool ImFontEx::LoadFromMemoryTTF(const void* data, size_t data_size,float fontSizeInPixels,int& bitmapW,int& bitmapH,int& bitmapN,ImVector<unsigned char>& bitmapOut,bool allowNonSquaredBitmap,const ImVector<int>& glyphs)
{
ImVector<unsigned char> ttfFileInMemory;
if (DataOwned) {
// Problem: Now we have the ttf file in the place where our binary file should be
// we should put it somewhere else:
ttfFileInMemory.resize(data_size);
memcpy((void*) &ttfFileInMemory[0],data,data_size);
Clear();DataSize=0;
data = (const void*) &ttfFileInMemory[0];
}
const unsigned char* pdata = (const unsigned char*) data;
// From now on we should only use pdata and data_size, but before that we should generate the bitmap
bitmapOut.clear();
stbtt_fontinfo font;
const int fontOffset = stbtt_GetFontOffsetForIndex(pdata,0);//0 // we only support the first charset inside the .ttf (otherwise it's just a question of exposing one more param)
if (!stbtt_InitFont(&font, pdata, fontOffset)) return false;
const int fheight = ttSHORT(font.data + font.hhea + 4) - ttSHORT(font.data + font.hhea + 6); // this should be the height of "font" AFAICU (see stbtt_ScaleForPixelHeight(...))
if (bitmapW<=0) bitmapW = 128;
if (bitmapH<=0) bitmapH = 128;
if (bitmapN<0 || bitmapN==2 || bitmapN>4) bitmapN=4;
IM_ASSERT(fheight>0 && fontSizeInPixels>0);
const float scale = fontSizeInPixels / fheight; // the scaling we must apply to the font
const ImVector<int>& pGlyphs = (glyphs.size()==0) ? GetDefaultGlyphs() : glyphs;
ImVector<stbtt_bakedchar> charData;
ImVector<bool> invalidGlyphs;
int rv = -1;bool even = true;
const int numComponents = 1; // This seems to be hard coded inside std_truetype: we can't change it here
bitmapOut.resize(bitmapW*bitmapH*numComponents);
while ((rv = stbtt_BakeFontBitmap2(font,scale,&bitmapOut[0],bitmapW,bitmapH, pGlyphs,charData,invalidGlyphs))<0) {
if (!allowNonSquaredBitmap) {bitmapW*=2;bitmapH*=2;}
else {
if (even) bitmapW*=2;
else bitmapH*=2;
even = !even;
}
bitmapOut.resize(bitmapW*bitmapH*numComponents);
}
// At this point bitmapOut contains a bitmap with numComponents==1. We must convert it if needed:
if (bitmapN>1) {
ImVector<unsigned char> bmp;bmp.resize(bitmapW*bitmapH*bitmapN);
size_t ni;
for (size_t i=0,isz=bitmapW*bitmapH;i<isz;i++) {
ni = bitmapN*i;
for (int c=0;c<bitmapN;c++) bmp[ni+c] = bitmapOut[i];
}
bitmapOut.swap(bmp);
}
// From now on we must use "pdata" and "data_size" only
// We must allocate a binary structure calculated from "pdata" and "data_size"
DataSize = sizeof(FntBlob);
if ((Data = (unsigned char*)ImGui::MemAlloc(DataSize)) == NULL) return false;
DataOwned = true; // Note: from now on whenever we "return false", we should call "Clear()" before.
FntBlob* pf = (FntBlob*) Data;
new(pf) FntBlob();
FntBlob& f = *pf;
// Now we must fill the "f" fields using "font" and "charData":
// this operation is impossible without knowing the internals of both ImGui and stb_truetype!
// Luckily for us only a limited number of fields need to be correct to make the ttf work!
// Please keep this in mind, as I'm going to fill MOST of the fields INCORRECTLY!
// we're going to do many float to integer casts. Better round them (in a portable way)
# define ROUNDCAST(X) (((X)>=0) ? ((X)+0.5f) : ((X)-0.5f))
f.glyphs.resize(charData.size()); // charData.size() <= number of glyphs
// f.info, such as: //info face="DejaVu Serif Condensed" size=16 bold=1 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
f.info.AA = 1;
f.info.BitField = 3; //unsigned char FntInfo::BitField; // bit 0: smooth, bit 1: unicode, bit 2: italic, bit 3: bold, bit 4: fixedHeight, bits 5-7: reserved
f.info.CharSet = 0;
f.info.FontSize = (signed short) ROUNDCAST(fontSizeInPixels);
f.info.Outline = 0;
f.info.PaddingDown = 0;
f.info.PaddingLeft = 0;
f.info.PaddingRight = 0;
f.info.PaddingUp = 0;
f.info.SpacingHoriz = 1;
f.info.SpacingVert = 1;
f.info.StretchH = 100; // The font height stretch in percentage. 100% means no stretch.
// f.common,such as: //common lineHeight=16 base=13 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=0 greenChnl=0 blueChnl=0
int ascent,descent,lineGap;
stbtt_GetFontVMetrics(&font, &ascent, &descent, &lineGap);
const float base = (float)(ascent)*scale;
f.common.Base = (unsigned short) ROUNDCAST(base);// (seems to match) The number of pixels from the absolute top of the line to the base of the characters.
f.common.Base = (unsigned short) ROUNDCAST((float)(ascent)*scale);// (seems to match) The number of pixels from the absolute top of the line to the base of the characters.
f.common.BitField = 0; // Set to 1 if the monochrome characters have been packed into each of the texture channels. In this case alphaChnl describes what is stored in each channel.
f.common.Channels[0] = 0; // alphaChnl: Set to 0 if the channel holds the glyph data, 1 if it holds the outline, 2 if it holds the glyph and the outline, 3 if its set to zero, and 4 if its set to one.
f.common.Channels[1] = 0; // redChnl: Set to 0 if the channel holds the glyph data, 1 if it holds the outline, 2 if it holds the glyph and the outline, 3 if its set to zero, and 4 if its set to one.
f.common.Channels[2] = 0; // greenChnl: Set to 0 if the channel holds the glyph data, 1 if it holds the outline, 2 if it holds the glyph and the outline, 3 if its set to zero, and 4 if its set to one.
f.common.Channels[3] = 0; // blueChnl: idem
f.common.LineHeight = (unsigned short) f.info.FontSize;// + lineGap*scale; ? //This is the distance in pixels between each line of text.
f.common.Pages = 1;
f.common.ScaleW = (unsigned short) bitmapW; // The width of the texture
f.common.ScaleH = (unsigned short) bitmapH; // The height of the texture
// f.glyphs, such as: //char id=33 x=120 y=56 width=4 height=12 xoffset=1 yoffset=2 xadvance=6 page=0 chnl=15
size_t charDataIndex=0;
for (size_t i=0,isz=pGlyphs.size();i<isz;i++) {
const int codepoint = pGlyphs[i];
if (invalidGlyphs[i]==true) continue; //skipped
const stbtt_bakedchar& bc = charData[charDataIndex++];
ImFont::FntGlyph& g = f.glyphs[i];
// Let's fill "g":
g.Id = (unsigned int) codepoint;
g.X = bc.x0;
g.Y = bc.y0;
g.Width = bc.x1-bc.x0;
g.Height = bc.y1-bc.y0;
g.XOffset = (signed short) ROUNDCAST(bc.xoff);
g.YOffset = (signed short) ROUNDCAST(bc.yoff+base); // fixes previous version's vertical alignment issue
g.XAdvance = (signed short) ROUNDCAST(bc.xadvance);
g.Page = 0;
g.Channel = 15; // The texture channel where the character image is found (1 = blue, 2 = green, 4 = red, 8 = alpha, 15 = all channels).
}
#undef ROUNDCAST
// No more wrong values from now on --------------------------------------------------------------
// Map f to struct fields:
Info = &f.info;Common = &f.common;
GlyphsCount=f.glyphs.size();Glyphs = GlyphsCount>0 ? &f.glyphs[0] : NULL;
KerningCount=f.kernings.size();Kerning = KerningCount>0 ? &f.kernings[0] : NULL;
Filenames.resize(f.filenames.size());for (size_t i=0;i<f.filenames.size();i++) Filenames[i] = &f.filenames[i][0];
dataIsNotBinFnt = true;
BuildLookupTable();
return true;
}
#endif //IMGUIFONTLOADER_USES_STB_TRUETYPE
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
#ifndef IMGUIFONTLOADER_H_
#define IMGUIFONTLOADER_H_
#include <imgui.h>
//WHAT'S THIS
/*
Extension to the imgui library v.1.19 wip (https://github.com/ocornut/imgui) to support:
-> both "text" and "binary" .fnt files (originally imgui supported only the "binary" .fnt format)
-> truetypefont (.ttf) files (experimental)
*/
/* TESTED ONLY WITH:
* .fnt generated by "Bitmap Font Generator v.1.14 beta"
* ImGui vesion v1.19 wip
*/
/* USAGE
This header (together with its cpp counterpart) can be used to load AngelFont .fnt files in either .bin and .text formats (the latter does not depend on any endianess).
Of course only a single texture page is supported, kernings are not supported and all the possible limitations that can ever be applied DO apply here.
Also note that since the base class "ImFont" is not polymorphic (= has no virtual methods), the usage is just a bit more complex than it could be:
// Before (worked only with "binary" .fnt files):
ImGuiIO& io = ImGui::GetIO();
io.Font = new ImFont();
io.Font->LoadFromFile(FNT_NAME);// or LoadFromMemory
// No cleanup is needed
// Now (autodetects "binary" or "text" .fnt formats):
#include <imguiloader.h> //somewhere in your file
ImGuiIO& io = ImGui::GetIO();
io.Font = new ImFontEx();
static_cast<ImFontEx*> (io.Font)->LoadFromFile(FNT_NAME); // or loadFromMemory
IM_ASSERT(io.Font->IsLoaded());
// cleanup is needed at the end, right before calling ImGui::Shutdown():
if (ImGui::GetIO().Font) static_cast<ImFontEx*> (ImGui::GetIO().Font)->Clear();
For further info please see the default demos that come with ImGui.
And remember that, as usual, the texture image must be loaded manually after the .fnt file.
*/
//#define IMGUIFONTLOADER_USES_STB_TRUETYPE // experimental. Needs <stb_truetype.h>
/* TTF USAGE (experimental)
#include <imguiloader.h> //somewhere in your file
// At init:
ImGuiIO& io = ImGui::GetIO();
io.Font = new ImFontEx();
float fontSizeInPixels=18.f;int bw=256,bh=256,bn=4;ImVector<unsigned char> ttbitmap;
bool ok = static_cast<ImFontEx*> (io.Font)->LoadFromFileTTF(TTF_FNT_PATH,fontSizeInPixels,bw,bh,bn,ttbitmap); // LoadFromMemoryTTF was never tested (but should work)
if (ok) {
IM_ASSERT(io.Font->IsLoaded());
int tex_x=bw, tex_y=bh;
void* tex_data = (void*) &ttbitmap[0];
// Automatically find white pixel from the texture we just loaded
// (io.Font->TexUvForWhite needs to contains UV coordinates pointing to a white pixel in order to render solid objects)
bool found = false;
for (int tex_data_off = 0; tex_data_off < tex_x*tex_y; tex_data_off++)
if (((unsigned int*)tex_data)[tex_data_off] == 0xffffffff)
{
io.Font->TexUvForWhite = ImVec2((float)(tex_data_off % tex_x)/(tex_x), (float)(tex_data_off / tex_x)/(tex_y));
found = true;
break;
}
if (!found) fprintf(stderr,"Error: white pixel not found in font image\n");
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_x, tex_y, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data);
}
else fprintf(stderr,"Error loading ttfont. (file not found ?)\n");
// At the end: cleanup is needed at the end, right before calling ImGui::Shutdown():
if (ImGui::GetIO().Font) static_cast<ImFontEx*> (ImGui::GetIO().Font)->Clear();
FIXED KNOWN PROBLEM AFFECTING LAST VERSION: When scaling the font (or the window) the vertical alignment of the font does not get lost anymore.
*/
//Warning: ImFont is not polymorphic
struct ImFontEx : public ImFont {
public:
IMGUI_API ImFontEx();
IMGUI_API ~ImFontEx();
IMGUI_API void Clear();
IMGUI_API bool LoadFromMemory(const void* data, size_t data_size);
IMGUI_API bool LoadFromFile(const char* filename);
# ifdef IMGUIFONTLOADER_USES_STB_TRUETYPE
// The returned bitmapW and bitmapH can be bigger than the input parameters.
// bitmapN is the number of components inside bitmapOut (can be 1,3 or 4: the latter is used when the input value is invalid).
IMGUI_API bool LoadFromFileTTF(const char* filename,float fontSizeInPixels,int& bitmapW,int& bitmapH,int& bitmapN,ImVector<unsigned char>& bitmapOut,bool allowNonSquaredBitmap=false,const ImVector<int>& glyphs=ImVector<int>());
IMGUI_API bool LoadFromMemoryTTF(const void* data, size_t data_size,float fontSizeInPixels,int& bitmapW,int& bitmapH,int& bitmapN,ImVector<unsigned char>& bitmapOut,bool allowNonSquaredBitmap=false,const ImVector<int>& glyphs=ImVector<int>());
static const ImVector<int>& GetDefaultGlyphs();
# endif //#IMGUIFONTLOADER_USES_STB_TRUETYPE
protected:
IMGUI_API bool LoadTextFntFileFromMemory(const void* data, size_t data_size);
bool dataIsNotBinFnt; // Binary .fnt was the only font supported by ImGui
IMGUI_API bool ConvertFileToMemory(const char* filename); // Just for internal refactoring purposes
};
#endif //IMGUIFONTLOADER_H_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment