Skip to content

Instantly share code, notes, and snippets.

@sorrge
Last active May 12, 2016 01:41
Show Gist options
  • Save sorrge/7056735 to your computer and use it in GitHub Desktop.
Save sorrge/7056735 to your computer and use it in GitHub Desktop.
Загрузка растеризованного шрифта, сделанного в BMFont ( http://www.angelcode.com/products/bmfont/ ) и его отрисовка с помощью OpenGL 3.3. Дополнительно необходима библиотека glm ( http://glm.g-truc.net/0.9.4/index.html ) и процедура загрузки картинок.
#version 330 core
// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec2 position2D;
layout(location = 1) in vec4 color;
layout(location = 2) in vec2 textureCoord;
// Output data
out vec4 pixColor;
out vec2 pixTextureCoord;
void main()
{
gl_Position = vec4(position2D, 0, 1);
pixColor = color;
pixTextureCoord = textureCoord;
}
/*
BASIC BMFont example implementation with Kerning, for C++ and OpenGL 2.0
Modified for OpenGL 3.3
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
--------------------------------------------------------------------------------
These editors can be used to generate BMFonts:
• http://www.angelcode.com/products/bmfont/ (free, windows)
• http://glyphdesigner.71squared.com/ (commercial, mac os x)
• http://www.n4te.com/hiero/hiero.jnlp (free, java, multiplatform)
• http://slick.cokeandcode.com/demos/hiero.jnlp (free, java, multiplatform)
Some code below based on code snippets from this gamedev posting:
http://www.gamedev.net/topic/330742-quick-tutorial-variable-width-bitmap-fonts/
Although I'm giving this away, I'd appreciate an email with fixes or better code!
aaedev@gmail.com 2012
*/
#include "stdafx.h"
#include "bmfont.h"
#include "FileStuff.h"
//Todo: Add buffer overflow checking.
#define MAX_BUFFER 256
bool BMFont::ParseFont(const string &fontfile )
{
std::ifstream Stream(fontfile);
std::string Line;
std::string Read, Key, Value;
std::size_t i;
int first,second,amount;
KearningInfo K;
CharDescriptor C;
while( !Stream.eof() )
{
std::stringstream LineStream;
std::getline( Stream, Line );
LineStream << Line;
//read the line's type
LineStream >> Read;
if( Read == "common" )
{
//this holds common data
while( !LineStream.eof() )
{
std::stringstream Converter;
LineStream >> Read;
i = Read.find( '=' );
Key = Read.substr( 0, i );
Value = Read.substr( i + 1 );
//assign the correct value
Converter << Value;
if( Key == "lineHeight" )
{Converter >> LineHeight;}
else if( Key == "base" )
{Converter >> Base;}
else if( Key == "scaleW" )
{Converter >> Width;}
else if( Key == "scaleH" )
{Converter >> Height;}
else if( Key == "pages" )
{Converter >> Pages;}
else if( Key == "outline" )
{Converter >> Outline;}
}
}
else if( Read == "char" )
{
//This is data for each specific character.
int CharID = 0;
while( !LineStream.eof() )
{
std::stringstream Converter;
LineStream >> Read;
i = Read.find( '=' );
Key = Read.substr( 0, i );
Value = Read.substr( i + 1 );
//Assign the correct value
Converter << Value;
if( Key == "id" )
{Converter >> CharID;}
else if( Key == "x" )
{ Converter >> C.x;}
else if( Key == "y" )
{ Converter >> C.y;}
else if( Key == "width" )
{ Converter >> C.Width;}
else if( Key == "height" )
{ Converter >> C.Height;}
else if( Key == "xoffset" )
{ Converter >> C.XOffset;}
else if( Key == "yoffset" )
{ Converter >> C.YOffset;}
else if( Key == "xadvance" )
{ Converter >> C.XAdvance;}
else if( Key == "page" )
{ Converter >> C.Page;}
}
Chars.insert(std::map<int,CharDescriptor>::value_type( CharID,C ));
}
else if( Read == "kernings" )
{
while( !LineStream.eof() )
{
std::stringstream Converter;
LineStream >> Read;
i = Read.find( '=' );
Key = Read.substr( 0, i );
Value = Read.substr( i + 1 );
//assign the correct value
Converter << Value;
if( Key == "count" )
{Converter >> KernCount; }
}
}
else if( Read == "kerning" )
{
while( !LineStream.eof() )
{
std::stringstream Converter;
LineStream >> Read;
i = Read.find( '=' );
Key = Read.substr( 0, i );
Value = Read.substr( i + 1 );
//assign the correct value
Converter << Value;
if( Key == "first" )
{Converter >> K.First; Converter >> first; }
else if( Key == "second" )
{Converter >> K.Second; Converter >> second; }
else if( Key == "amount" )
{Converter >> K.Amount; Converter >> amount;}
}
//LOG_DEBUG("Done with this pass");
Kearn.push_back(K);
}
else if(Read == "page")
{
int id;
string file;
while( !LineStream.eof() )
{
std::stringstream Converter;
LineStream >> Read;
i = Read.find( '=' );
Key = Read.substr( 0, i );
Value = Read.substr( i + 1 );
//assign the correct value
Converter << Value;
if( Key == "id" )
{
Converter >> id;
if(id != 0)
throw "Only single page fonts are supported at the moment";
}
else if( Key == "file" )
{
Converter >> file;
boost::algorithm::erase_all(file, "\"");
size_t found;
found=fontfile.find_last_of("/\\");
auto dir = fontfile.substr(0,found);
string png = dir.empty() ? file : dir + "/" + file;
ftexid = pngTextureLoad(png, 0, 0, true);
}
}
}
}
Stream.close();
return true;
}
int BMFont::GetKerningPair(int first, int second)
{
if ( KernCount && UseKern ) //Only process if there actually is kerning information
{
//Kearning is checked for every character processed. This is expensive in terms of processing time.
for (int j = 0; j < KernCount; j++ )
{
if (Kearn[j].First == first && Kearn[j].Second == second)
{
//LOG_DEBUG("Kerning Pair Found!!!");kerning._Left
// LOG_DEBUG("FIRST: %d SECOND: %d offset %d",first,second,Kearn[j].Amount);
return Kearn[j].Amount;
}
}
}
return 0;
}
float BMFont::GetStringWidth(const char *string)
{
float total=0;
CharDescriptor *f;
for (int i = 0; i != strlen(string); i++)
{
f=&Chars[string[i]];
total+=f->XAdvance;
}
return total * fscale;
}
bool BMFont::LoadFont(const string &fontfile)
{
std::ifstream Stream(fontfile);
if ( !Stream.is_open() )
{
throw "Cannot Find Font File " + fontfile;
}
Stream.close();
//LOG_DEBUG("Starting to Parse Font %s",fontfile);
ParseFont(fontfile);
//LOG_DEBUG("Finished Parsing Font %s",fontfile);
KernCount = (int) Kearn.size();
return true;
}
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
void BMFont::Render_String(size_t len)
{
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//Enable Client States
glUseProgram(shader);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, txlist.size() * sizeof(txdata), &txlist[0].x, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_TRUE, sizeof(txdata), 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(txdata), BUFFER_OFFSET(sizeof(float) * 4));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(txdata), BUFFER_OFFSET(sizeof(float) * 2));
glBindTexture(GL_TEXTURE_2D, ftexid);
GLint uniform_mytexture = glGetUniformLocation(shader, "textureSampler");
glUniform1i(uniform_mytexture, /*GL_TEXTURE*/0);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)len * 6);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
}
void BMFont::Print(float x, float y, const char *fmt, ...)
{
float CurX = (float) x;
float CurY = (float) y;
float DstX = 0.0;
float DstY = 0.0;
size_t Flen;
float adv = (float) 1.0/Width; // Font texture atlas spacing.
char text[512] = ""; // Holds Our String
CharDescriptor *f; // Pointer to font.
va_list ap; // Pointer To List Of Arguments
if (fmt == NULL) // If There's No Text
return; // Do Nothing
va_start(ap, fmt); // Parses The String For Variables
vsprintf_s(text, fmt, ap); // And Converts Symbols To Actual Numbers
va_end(ap);
//y= y + LineHeight; //This can be used to flip rendering
Flen = strlen(text);
//Todo: Add caching to make this much faster
txlist.clear();
for (size_t i = 0; i != Flen; ++i)
{
f=&Chars[text[i]];
CurX = x + f->XOffset;
CurY = y + f->YOffset;
DstX = CurX + f->Width;
DstY = CurY + f->Height;
vec2 fCur = IntCoordToFloat(vec2(CurX, CurY)) * fscale;
vec2 fDst = IntCoordToFloat(vec2(DstX, DstY)) * fscale;
txlist.push_back(txdata(fCur.x, fDst.y, adv*f->x, 1.0f -(adv*(f->y+f->Height)), fcolor));
txlist.push_back(txdata(fCur.x, fCur.y, adv*f->x, 1.0f -(adv*f->y), fcolor));
txlist.push_back(txdata(fDst.x, fCur.y, adv*(f->x+f->Width),1.0f -(adv*f->y), fcolor));
txlist.push_back(txdata(fCur.x, fDst.y, adv*f->x, 1.0f -(adv*(f->y+f->Height)), fcolor));
txlist.push_back(txdata(fDst.x, fCur.y, adv*(f->x+f->Width),1.0f -(adv*f->y), fcolor));
txlist.push_back(txdata(fDst.x, fDst.y, adv*(f->x+f->Width),1.0f-(adv*(f->y+f->Height)), fcolor));
//Only check kerning if there is greater then 1 character and
//if the check character is 1 less then the end of the string.
if (Flen > 1 && i < Flen)
x += GetKerningPair(text[i],text[i+1]);
x += f->XAdvance;
}
Render_String(strlen(text));
}
void BMFont::PrintCenter(float y, const char *string)
{
int x = 0;
CharDescriptor *f;
size_t len = strlen(string);
for (size_t i = 0; i != len; ++i)
{
f=&Chars[string[i]];
if (len > 1 && i < len)
x += GetKerningPair(string[i],string[i+1]);
x += f->XAdvance;
}
Print( (float)(windowDimensions.x/2) - (x/2) , y, string);
}
vec2 BMFont::IntCoordToFloat(const vec2& iPos)
{
return vec2(iPos.x / windowDimensions.x * 2 - 1, -iPos.y / windowDimensions.y * 2 + 1);
}
BMFont::~BMFont()
{
Chars.clear();
Kearn.clear();
txlist.clear();
glDeleteProgram(shader);
glDeleteTextures(1, &ftexid);
glDeleteBuffers(1, &vertexBuffer);
}
/*
BMFont example implementation with Kerning, for C++ and OpenGL 2.0
Modified for OpenGL 3.3
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
#ifndef __BMFONT__
#define __BMFONT__
#include "FileStuff.h"
#ifndef MAKE_RGBA
#define MAKE_RGBA(r,g,b,a) (r | (g << 8) | (b << 16) | (a << 24))
#endif
#ifndef GET_BLUE
#define GET_BLUE(rgba) (( (rgba)>>16 ) & 0xff )
#endif
#ifndef GET_GREEN
#define GET_GREEN(rgba) (( (rgba)>>8 ) & 0xff )
#endif
#ifndef GET_RED
#define GET_RED(rgba) ( rgba & 0xff )
#endif
#ifndef GET_ALPHA
#define GET_ALPHA(rgba) (( (rgba)>>24 ) & 0xff)
#endif
struct txdata {
float x, y;
float tx,ty;
vec4 color;
txdata(float x, float y, float tx, float ty, const vec4 &_color)
{
this->x = x;
this->y = y;
this->tx = tx;
this->ty = ty;
color = _color;
}
};
class KearningInfo
{
public:
short First;
short Second;
short Amount;
KearningInfo() : First( 0 ), Second( 0 ), Amount( 0 ) { }
};
class CharDescriptor
{
public:
short x, y;
short Width;
short Height;
short XOffset;
short YOffset;
short XAdvance;
short Page;
CharDescriptor() : x( 0 ), y( 0 ), Width( 0 ), Height( 0 ), XOffset( 0 ), YOffset( 0 ),
XAdvance( 0 ), Page( 0 )
{ }
};
class BMFont
{
public:
bool LoadFont(const string& fontFile);
void SetColor(const vec4& color) {fcolor = color;}
void SetBlend(int b) {fblend = b;}
void SetScale(float scale){fscale = scale;}
float GetHeight(){return LineHeight * fscale;}
void UseKerning(bool b) {UseKern = b;}
void Print(float, float, const char *,...);
void PrintCenter( float, const char *);
float GetStringWidth(const char *);
BMFont(const uvec2& _windowDimensions)
{
glGenBuffers(1, &vertexBuffer);
shader = LoadShaders("shaders/2dtexcol-vert.glsl", "shaders/texcol-frag.glsl");
SetColor(vec4(1, 1, 1, 1));
windowDimensions = _windowDimensions;
KernCount = 0;
ftexid = -1;
fblend = 0;
fscale = 1;
UseKern = true;
};
virtual ~BMFont();
private:
short LineHeight;
short Base;
short Width;
short Height;
short Pages;
short Outline;
short KernCount;
bool UseKern;
std::map<int,CharDescriptor> Chars;
std::vector<KearningInfo> Kearn;
vec4 fcolor;
GLuint ftexid;
float fscale;
int fblend;
void BMFont::ReplaceExtension(std::string &str, std::string rep);
char* BMFont::replace_str(char *str, char *orig, char *rep);
void Render_String(size_t len);
bool ParseFont(const string &fontFile);
int GetKerningPair(int, int);
vec2 IntCoordToFloat(const vec2& iPos);
std::vector<txdata> txlist;
GLuint vertexBuffer;
GLuint shader;
uvec2 windowDimensions;
};
#endif
...
uvec2 windowSize = ...
BMFont font(windowSize);
if(!font.LoadFont("fonts/arial32.fnt"))
throw "Can't load font";
font.Print(0, 30, "Hello");
#version 330 core
in vec4 pixColor;
in vec2 pixTextureCoord;
uniform sampler2D textureSampler;
out vec4 color;
void main()
{
color = pixColor * texture(textureSampler, pixTextureCoord).rgba;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment