2010-11-20 10:37:14 +00:00
|
|
|
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
|
|
|
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
2010-05-29 07:25:38 +00:00
|
|
|
#include <base/system.h>
|
|
|
|
#include <base/math.h>
|
|
|
|
#include <engine/graphics.h>
|
|
|
|
#include <engine/textrender.h>
|
|
|
|
|
|
|
|
#ifdef CONF_FAMILY_WINDOWS
|
|
|
|
#include <windows.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONF_PLATFORM_MACOSX
|
|
|
|
#include <OpenGL/gl.h>
|
|
|
|
#include <OpenGL/glu.h>
|
|
|
|
#else
|
|
|
|
#include <GL/gl.h>
|
|
|
|
#include <GL/glu.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// ft2 texture
|
|
|
|
#include <ft2build.h>
|
|
|
|
#include FT_FREETYPE_H
|
|
|
|
|
|
|
|
// TODO: Refactor: clean this up
|
|
|
|
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
MAX_CHARACTERS = 64,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2011-01-04 11:30:40 +00:00
|
|
|
static int aFontSizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36,64};
|
2010-05-29 07:25:38 +00:00
|
|
|
#define NUM_FONT_SIZES (sizeof(aFontSizes)/sizeof(int))
|
|
|
|
|
|
|
|
struct CFontChar
|
|
|
|
{
|
2011-02-12 10:40:36 +00:00
|
|
|
int m_ID;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
// these values are scaled to the pFont size
|
|
|
|
// width * font_size == real_size
|
|
|
|
float m_Width;
|
|
|
|
float m_Height;
|
|
|
|
float m_OffsetX;
|
|
|
|
float m_OffsetY;
|
|
|
|
float m_AdvanceX;
|
|
|
|
|
|
|
|
float m_aUvs[4];
|
|
|
|
int64 m_TouchTime;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CFontSizeData
|
|
|
|
{
|
|
|
|
int m_FontSize;
|
|
|
|
FT_Face *m_pFace;
|
|
|
|
|
2010-06-01 19:55:08 +00:00
|
|
|
GLuint m_aTextures[2];
|
2010-05-29 07:25:38 +00:00
|
|
|
int m_TextureWidth;
|
|
|
|
int m_TextureHeight;
|
|
|
|
|
|
|
|
int m_NumXChars;
|
|
|
|
int m_NumYChars;
|
|
|
|
|
|
|
|
int m_CharMaxWidth;
|
|
|
|
int m_CharMaxHeight;
|
|
|
|
|
|
|
|
CFontChar m_aCharacters[MAX_CHARACTERS*MAX_CHARACTERS];
|
|
|
|
|
|
|
|
int m_CurrentCharacter;
|
|
|
|
};
|
|
|
|
|
2010-07-05 18:57:07 +00:00
|
|
|
class CFont
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2010-07-05 18:57:07 +00:00
|
|
|
public:
|
2010-10-06 21:07:35 +00:00
|
|
|
char m_aFilename[512];
|
2010-05-29 07:25:38 +00:00
|
|
|
FT_Face m_FtFace;
|
|
|
|
CFontSizeData m_aSizes[NUM_FONT_SIZES];
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class CTextRender : public IEngineTextRender
|
|
|
|
{
|
|
|
|
IGraphics *m_pGraphics;
|
|
|
|
IGraphics *Graphics() { return m_pGraphics; }
|
|
|
|
|
|
|
|
int WordLength(const char *pText)
|
|
|
|
{
|
|
|
|
int s = 1;
|
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
if(*pText == 0)
|
|
|
|
return s-1;
|
|
|
|
if(*pText == '\n' || *pText == '\t' || *pText == ' ')
|
|
|
|
return s;
|
|
|
|
pText++;
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
float m_TextR;
|
|
|
|
float m_TextG;
|
|
|
|
float m_TextB;
|
|
|
|
float m_TextA;
|
|
|
|
|
|
|
|
int m_FontTextureFormat;
|
|
|
|
|
2010-07-05 18:57:07 +00:00
|
|
|
CFont *m_pDefaultFont;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
FT_Library m_FTLibrary;
|
|
|
|
|
|
|
|
int GetFontSizeIndex(int Pixelsize)
|
|
|
|
{
|
|
|
|
for(unsigned i = 0; i < NUM_FONT_SIZES; i++)
|
|
|
|
{
|
|
|
|
if(aFontSizes[i] >= Pixelsize)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NUM_FONT_SIZES-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void Grow(unsigned char *pIn, unsigned char *pOut, int w, int h)
|
|
|
|
{
|
|
|
|
for(int y = 0; y < h; y++)
|
|
|
|
for(int x = 0; x < w; x++)
|
|
|
|
{
|
|
|
|
int c = pIn[y*w+x];
|
|
|
|
|
|
|
|
for(int sy = -1; sy <= 1; sy++)
|
|
|
|
for(int sx = -1; sx <= 1; sx++)
|
|
|
|
{
|
|
|
|
int GetX = x+sx;
|
|
|
|
int GetY = y+sy;
|
|
|
|
if (GetX >= 0 && GetY >= 0 && GetX < w && GetY < h)
|
|
|
|
{
|
|
|
|
int Index = GetY*w+GetX;
|
|
|
|
if(pIn[Index] > c)
|
|
|
|
c = pIn[Index];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pOut[y*w+x] = c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InitTexture(CFontSizeData *pSizeData, int CharWidth, int CharHeight, int Xchars, int Ychars)
|
|
|
|
{
|
|
|
|
static int FontMemoryUsage = 0;
|
|
|
|
int Width = CharWidth*Xchars;
|
|
|
|
int Height = CharHeight*Ychars;
|
|
|
|
void *pMem = mem_alloc(Width*Height, 1);
|
|
|
|
mem_zero(pMem, Width*Height);
|
|
|
|
|
|
|
|
if(pSizeData->m_aTextures[0] == 0)
|
|
|
|
glGenTextures(2, pSizeData->m_aTextures);
|
|
|
|
else
|
|
|
|
FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight*2;
|
|
|
|
|
|
|
|
pSizeData->m_NumXChars = Xchars;
|
|
|
|
pSizeData->m_NumYChars = Ychars;
|
|
|
|
pSizeData->m_TextureWidth = Width;
|
|
|
|
pSizeData->m_TextureHeight = Height;
|
|
|
|
pSizeData->m_CurrentCharacter = 0;
|
|
|
|
|
|
|
|
for(int i = 0; i < 2; i++)
|
|
|
|
{
|
|
|
|
glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[i]);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, m_FontTextureFormat, Width, Height, 0, m_FontTextureFormat, GL_UNSIGNED_BYTE, pMem);
|
|
|
|
FontMemoryUsage += Width*Height;
|
|
|
|
}
|
|
|
|
|
|
|
|
dbg_msg("", "pFont memory usage: %d", FontMemoryUsage);
|
|
|
|
|
|
|
|
mem_free(pMem);
|
|
|
|
}
|
|
|
|
|
2011-01-29 17:48:55 +00:00
|
|
|
int AdjustOutlineThicknessToFontSize(int OutlineThickness, int FontSize)
|
|
|
|
{
|
|
|
|
if(FontSize > 36)
|
|
|
|
OutlineThickness *= 4;
|
|
|
|
else if(FontSize >= 18)
|
|
|
|
OutlineThickness *= 2;
|
|
|
|
return OutlineThickness;
|
2011-01-04 11:30:40 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void IncreaseTextureSize(CFontSizeData *pSizeData)
|
|
|
|
{
|
|
|
|
if(pSizeData->m_TextureWidth < pSizeData->m_TextureHeight)
|
|
|
|
pSizeData->m_NumXChars <<= 1;
|
|
|
|
else
|
|
|
|
pSizeData->m_NumYChars <<= 1;
|
|
|
|
InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, pSizeData->m_NumXChars, pSizeData->m_NumYChars);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Refactor: move this into a pFont class
|
|
|
|
void InitIndex(CFont *pFont, int Index)
|
|
|
|
{
|
|
|
|
CFontSizeData *pSizeData = &pFont->m_aSizes[Index];
|
|
|
|
|
|
|
|
pSizeData->m_FontSize = aFontSizes[Index];
|
|
|
|
FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize);
|
|
|
|
|
2011-01-04 11:30:40 +00:00
|
|
|
int OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
unsigned GlyphIndex;
|
|
|
|
int MaxH = 0;
|
|
|
|
int MaxW = 0;
|
|
|
|
|
|
|
|
int Charcode = FT_Get_First_Char(pFont->m_FtFace, &GlyphIndex);
|
|
|
|
while(GlyphIndex != 0)
|
|
|
|
{
|
|
|
|
// do stuff
|
|
|
|
FT_Load_Glyph(pFont->m_FtFace, GlyphIndex, FT_LOAD_DEFAULT);
|
|
|
|
|
|
|
|
if(pFont->m_FtFace->glyph->metrics.width > MaxW) MaxW = pFont->m_FtFace->glyph->metrics.width; // ignore_convention
|
|
|
|
if(pFont->m_FtFace->glyph->metrics.height > MaxH) MaxH = pFont->m_FtFace->glyph->metrics.height; // ignore_convention
|
|
|
|
Charcode = FT_Get_Next_Char(pFont->m_FtFace, Charcode, &GlyphIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
MaxW = (MaxW>>6)+2+OutlineThickness*2;
|
|
|
|
MaxH = (MaxH>>6)+2+OutlineThickness*2;
|
|
|
|
|
|
|
|
for(pSizeData->m_CharMaxWidth = 1; pSizeData->m_CharMaxWidth < MaxW; pSizeData->m_CharMaxWidth <<= 1);
|
|
|
|
for(pSizeData->m_CharMaxHeight = 1; pSizeData->m_CharMaxHeight < MaxH; pSizeData->m_CharMaxHeight <<= 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
//dbg_msg("pFont", "init size %d, texture size %d %d", pFont->sizes[index].font_size, w, h);
|
|
|
|
//FT_New_Face(m_FTLibrary, "data/fonts/vera.ttf", 0, &pFont->ft_face);
|
|
|
|
InitTexture(pSizeData, pSizeData->m_CharMaxWidth, pSizeData->m_CharMaxHeight, 8, 8);
|
|
|
|
}
|
|
|
|
|
|
|
|
CFontSizeData *GetSize(CFont *pFont, int Pixelsize)
|
|
|
|
{
|
|
|
|
int Index = GetFontSizeIndex(Pixelsize);
|
|
|
|
if(pFont->m_aSizes[Index].m_FontSize != aFontSizes[Index])
|
|
|
|
InitIndex(pFont, Index);
|
|
|
|
return &pFont->m_aSizes[Index];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-02-12 10:40:36 +00:00
|
|
|
void UploadGlyph(CFontSizeData *pSizeData, int Texnum, int SlotID, int Chr, const void *pData)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2011-02-12 10:40:36 +00:00
|
|
|
int x = (SlotID%pSizeData->m_NumXChars) * (pSizeData->m_TextureWidth/pSizeData->m_NumXChars);
|
|
|
|
int y = (SlotID/pSizeData->m_NumXChars) * (pSizeData->m_TextureHeight/pSizeData->m_NumYChars);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[Texnum]);
|
|
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
|
|
|
|
pSizeData->m_TextureWidth/pSizeData->m_NumXChars,
|
|
|
|
pSizeData->m_TextureHeight/pSizeData->m_NumYChars,
|
|
|
|
m_FontTextureFormat, GL_UNSIGNED_BYTE, pData);
|
|
|
|
}
|
|
|
|
|
2010-10-29 21:49:33 +00:00
|
|
|
// 32k of data used for rendering glyphs
|
|
|
|
unsigned char ms_aGlyphData[(1024/8) * (1024/8)];
|
|
|
|
unsigned char ms_aGlyphDataOutlined[(1024/8) * (1024/8)];
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
int GetSlot(CFontSizeData *pSizeData)
|
|
|
|
{
|
|
|
|
int CharCount = pSizeData->m_NumXChars*pSizeData->m_NumYChars;
|
|
|
|
if(pSizeData->m_CurrentCharacter < CharCount)
|
|
|
|
{
|
|
|
|
int i = pSizeData->m_CurrentCharacter;
|
|
|
|
pSizeData->m_CurrentCharacter++;
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// kick out the oldest
|
|
|
|
// TODO: remove this linear search
|
|
|
|
{
|
|
|
|
int Oldest = 0;
|
|
|
|
for(int i = 1; i < CharCount; i++)
|
|
|
|
{
|
|
|
|
if(pSizeData->m_aCharacters[i].m_TouchTime < pSizeData->m_aCharacters[Oldest].m_TouchTime)
|
|
|
|
Oldest = i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(time_get()-pSizeData->m_aCharacters[Oldest].m_TouchTime < time_freq())
|
|
|
|
{
|
|
|
|
IncreaseTextureSize(pSizeData);
|
|
|
|
return GetSlot(pSizeData);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Oldest;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int RenderGlyph(CFont *pFont, CFontSizeData *pSizeData, int Chr)
|
|
|
|
{
|
|
|
|
FT_Bitmap *pBitmap;
|
2011-02-12 10:40:36 +00:00
|
|
|
int SlotID = 0;
|
2010-05-29 07:25:38 +00:00
|
|
|
int SlotW = pSizeData->m_TextureWidth / pSizeData->m_NumXChars;
|
|
|
|
int SlotH = pSizeData->m_TextureHeight / pSizeData->m_NumYChars;
|
|
|
|
int SlotSize = SlotW*SlotH;
|
|
|
|
int x = 1;
|
|
|
|
int y = 1;
|
|
|
|
int px, py;
|
|
|
|
|
|
|
|
FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, pSizeData->m_FontSize);
|
|
|
|
|
|
|
|
if(FT_Load_Char(pFont->m_FtFace, Chr, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP))
|
|
|
|
{
|
|
|
|
dbg_msg("pFont", "error loading glyph %d", Chr);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pBitmap = &pFont->m_FtFace->glyph->bitmap; // ignore_convention
|
|
|
|
|
|
|
|
// fetch slot
|
2011-02-12 10:40:36 +00:00
|
|
|
SlotID = GetSlot(pSizeData);
|
|
|
|
if(SlotID < 0)
|
2010-05-29 07:25:38 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
// adjust spacing
|
2011-01-04 11:30:40 +00:00
|
|
|
int OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize);
|
2010-05-29 07:25:38 +00:00
|
|
|
x += OutlineThickness;
|
|
|
|
y += OutlineThickness;
|
|
|
|
|
|
|
|
// prepare glyph data
|
|
|
|
mem_zero(ms_aGlyphData, SlotSize);
|
|
|
|
|
|
|
|
if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) // ignore_convention
|
|
|
|
{
|
|
|
|
for(py = 0; py < pBitmap->rows; py++) // ignore_convention
|
|
|
|
for(px = 0; px < pBitmap->width; px++) // ignore_convention
|
|
|
|
ms_aGlyphData[(py+y)*SlotW+px+x] = pBitmap->buffer[py*pBitmap->pitch+px]; // ignore_convention
|
|
|
|
}
|
|
|
|
else if(pBitmap->pixel_mode == FT_PIXEL_MODE_MONO) // ignore_convention
|
|
|
|
{
|
|
|
|
for(py = 0; py < pBitmap->rows; py++) // ignore_convention
|
|
|
|
for(px = 0; px < pBitmap->width; px++) // ignore_convention
|
|
|
|
{
|
|
|
|
if(pBitmap->buffer[py*pBitmap->pitch+px/8]&(1<<(7-(px%8)))) // ignore_convention
|
|
|
|
ms_aGlyphData[(py+y)*SlotW+px+x] = 255;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(0) for(py = 0; py < SlotW; py++)
|
|
|
|
for(px = 0; px < SlotH; px++)
|
|
|
|
ms_aGlyphData[py*SlotW+px] = 255;
|
|
|
|
|
|
|
|
// upload the glyph
|
2011-02-12 10:40:36 +00:00
|
|
|
UploadGlyph(pSizeData, 0, SlotID, Chr, ms_aGlyphData);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
if(OutlineThickness == 1)
|
|
|
|
{
|
|
|
|
Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH);
|
2011-02-12 10:40:36 +00:00
|
|
|
UploadGlyph(pSizeData, 1, SlotID, Chr, ms_aGlyphDataOutlined);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2011-01-05 11:12:23 +00:00
|
|
|
for(int i = OutlineThickness; i > 0; i-=2)
|
|
|
|
{
|
|
|
|
Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH);
|
|
|
|
Grow(ms_aGlyphDataOutlined, ms_aGlyphData, SlotW, SlotH);
|
|
|
|
}
|
2011-02-12 10:40:36 +00:00
|
|
|
UploadGlyph(pSizeData, 1, SlotID, Chr, ms_aGlyphData);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// set char info
|
|
|
|
{
|
2011-02-12 10:40:36 +00:00
|
|
|
CFontChar *pFontchr = &pSizeData->m_aCharacters[SlotID];
|
2010-05-29 07:25:38 +00:00
|
|
|
float Scale = 1.0f/pSizeData->m_FontSize;
|
|
|
|
float Uscale = 1.0f/pSizeData->m_TextureWidth;
|
|
|
|
float Vscale = 1.0f/pSizeData->m_TextureHeight;
|
|
|
|
int Height = pBitmap->rows + OutlineThickness*2 + 2; // ignore_convention
|
|
|
|
int Width = pBitmap->width + OutlineThickness*2 + 2; // ignore_convention
|
|
|
|
|
2011-02-12 10:40:36 +00:00
|
|
|
pFontchr->m_ID = Chr;
|
2010-05-29 07:25:38 +00:00
|
|
|
pFontchr->m_Height = Height * Scale;
|
|
|
|
pFontchr->m_Width = Width * Scale;
|
|
|
|
pFontchr->m_OffsetX = (pFont->m_FtFace->glyph->bitmap_left-1) * Scale; // ignore_convention
|
|
|
|
pFontchr->m_OffsetY = (pSizeData->m_FontSize - pFont->m_FtFace->glyph->bitmap_top) * Scale; // ignore_convention
|
|
|
|
pFontchr->m_AdvanceX = (pFont->m_FtFace->glyph->advance.x>>6) * Scale; // ignore_convention
|
|
|
|
|
2011-02-12 10:40:36 +00:00
|
|
|
pFontchr->m_aUvs[0] = (SlotID%pSizeData->m_NumXChars) / (float)(pSizeData->m_NumXChars);
|
|
|
|
pFontchr->m_aUvs[1] = (SlotID/pSizeData->m_NumXChars) / (float)(pSizeData->m_NumYChars);
|
2010-05-29 07:25:38 +00:00
|
|
|
pFontchr->m_aUvs[2] = pFontchr->m_aUvs[0] + Width*Uscale;
|
|
|
|
pFontchr->m_aUvs[3] = pFontchr->m_aUvs[1] + Height*Vscale;
|
|
|
|
}
|
|
|
|
|
2011-02-12 10:40:36 +00:00
|
|
|
return SlotID;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CFontChar *GetChar(CFont *pFont, CFontSizeData *pSizeData, int Chr)
|
|
|
|
{
|
|
|
|
CFontChar *pFontchr = NULL;
|
|
|
|
|
|
|
|
// search for the character
|
|
|
|
// TODO: remove this linear search
|
|
|
|
int i;
|
|
|
|
for(i = 0; i < pSizeData->m_CurrentCharacter; i++)
|
|
|
|
{
|
2011-02-12 10:40:36 +00:00
|
|
|
if(pSizeData->m_aCharacters[i].m_ID == Chr)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
pFontchr = &pSizeData->m_aCharacters[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if we need to render the character
|
|
|
|
if(!pFontchr)
|
|
|
|
{
|
|
|
|
int Index = RenderGlyph(pFont, pSizeData, Chr);
|
|
|
|
if(Index >= 0)
|
|
|
|
pFontchr = &pSizeData->m_aCharacters[Index];
|
|
|
|
}
|
|
|
|
|
|
|
|
// touch the character
|
|
|
|
// TODO: don't call time_get here
|
|
|
|
if(pFontchr)
|
|
|
|
pFontchr->m_TouchTime = time_get();
|
|
|
|
|
|
|
|
return pFontchr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// must only be called from the rendering function as the pFont must be set to the correct size
|
|
|
|
void RenderSetup(CFont *pFont, int size)
|
|
|
|
{
|
|
|
|
FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
float Kerning(CFont *pFont, int Left, int Right)
|
|
|
|
{
|
|
|
|
FT_Vector Kerning = {0,0};
|
|
|
|
FT_Get_Kerning(pFont->m_FtFace, Left, Right, FT_KERNING_DEFAULT, &Kerning);
|
|
|
|
return (Kerning.x>>6);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
CTextRender()
|
|
|
|
{
|
|
|
|
m_pGraphics = 0;
|
|
|
|
|
|
|
|
m_TextR = 1;
|
|
|
|
m_TextG = 1;
|
|
|
|
m_TextB = 1;
|
|
|
|
m_TextA = 1;
|
|
|
|
|
|
|
|
m_pDefaultFont = 0;
|
|
|
|
|
|
|
|
// GL_LUMINANCE can be good for debugging
|
|
|
|
m_FontTextureFormat = GL_ALPHA;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void Init()
|
|
|
|
{
|
|
|
|
m_pGraphics = Kernel()->RequestInterface<IGraphics>();
|
|
|
|
FT_Init_FreeType(&m_FTLibrary);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
virtual CFont *LoadFont(const char *pFilename)
|
|
|
|
{
|
|
|
|
CFont *pFont = (CFont *)mem_alloc(sizeof(CFont), 1);
|
|
|
|
|
|
|
|
mem_zero(pFont, sizeof(*pFont));
|
|
|
|
str_copy(pFont->m_aFilename, pFilename, sizeof(pFont->m_aFilename));
|
|
|
|
|
|
|
|
if(FT_New_Face(m_FTLibrary, pFont->m_aFilename, 0, &pFont->m_FtFace))
|
|
|
|
{
|
|
|
|
mem_free(pFont);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(unsigned i = 0; i < NUM_FONT_SIZES; i++)
|
|
|
|
pFont->m_aSizes[i].m_FontSize = -1;
|
|
|
|
|
|
|
|
dbg_msg("textrender", "loaded pFont from '%s'", pFilename);
|
|
|
|
return pFont;
|
|
|
|
};
|
|
|
|
|
|
|
|
virtual void DestroyFont(CFont *pFont)
|
|
|
|
{
|
|
|
|
mem_free(pFont);
|
|
|
|
}
|
|
|
|
|
2010-07-05 18:57:07 +00:00
|
|
|
virtual void SetDefaultFont(CFont *pFont)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
dbg_msg("textrender", "default pFont set %p", pFont);
|
|
|
|
m_pDefaultFont = pFont;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
virtual void SetCursor(CTextCursor *pCursor, float x, float y, float FontSize, int Flags)
|
|
|
|
{
|
|
|
|
mem_zero(pCursor, sizeof(*pCursor));
|
|
|
|
pCursor->m_FontSize = FontSize;
|
|
|
|
pCursor->m_StartX = x;
|
|
|
|
pCursor->m_StartY = y;
|
|
|
|
pCursor->m_X = x;
|
|
|
|
pCursor->m_Y = y;
|
|
|
|
pCursor->m_LineCount = 1;
|
|
|
|
pCursor->m_LineWidth = -1;
|
|
|
|
pCursor->m_Flags = Flags;
|
|
|
|
pCursor->m_CharCount = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
virtual void Text(void *pFontSetV, float x, float y, float Size, const char *pText, int MaxWidth)
|
|
|
|
{
|
|
|
|
CTextCursor Cursor;
|
|
|
|
SetCursor(&Cursor, x, y, Size, TEXTFLAG_RENDER);
|
|
|
|
Cursor.m_LineWidth = MaxWidth;
|
|
|
|
TextEx(&Cursor, pText, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual float TextWidth(void *pFontSetV, float Size, const char *pText, int Length)
|
|
|
|
{
|
|
|
|
CTextCursor Cursor;
|
|
|
|
SetCursor(&Cursor, 0, 0, Size, 0);
|
|
|
|
TextEx(&Cursor, pText, Length);
|
|
|
|
return Cursor.m_X;
|
|
|
|
}
|
|
|
|
|
2010-08-16 00:21:18 +00:00
|
|
|
virtual int TextLineCount(void *pFontSetV, float Size, const char *pText, float LineWidth)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
CTextCursor Cursor;
|
|
|
|
SetCursor(&Cursor, 0, 0, Size, 0);
|
|
|
|
Cursor.m_LineWidth = LineWidth;
|
|
|
|
TextEx(&Cursor, pText, -1);
|
|
|
|
return Cursor.m_LineCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void TextColor(float r, float g, float b, float a)
|
|
|
|
{
|
|
|
|
m_TextR = r;
|
|
|
|
m_TextG = g;
|
|
|
|
m_TextB = b;
|
|
|
|
m_TextA = a;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void TextEx(CTextCursor *pCursor, const char *pText, int Length)
|
|
|
|
{
|
|
|
|
CFont *pFont = pCursor->m_pFont;
|
|
|
|
CFontSizeData *pSizeData = NULL;
|
|
|
|
|
|
|
|
//dbg_msg("textrender", "rendering text '%s'", text);
|
|
|
|
|
|
|
|
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
|
|
|
|
float FakeToScreenX, FakeToScreenY;
|
|
|
|
int ActualX, ActualY;
|
|
|
|
|
|
|
|
int ActualSize;
|
|
|
|
int i;
|
|
|
|
int GotNewLine = 0;
|
2010-11-17 17:36:19 +00:00
|
|
|
float DrawX = 0.0f, DrawY = 0.0f;
|
|
|
|
int LineCount = 0;
|
2010-05-29 07:25:38 +00:00
|
|
|
float CursorX, CursorY;
|
|
|
|
const char *pEnd;
|
|
|
|
|
|
|
|
float Size = pCursor->m_FontSize;
|
|
|
|
|
|
|
|
// to correct coords, convert to screen coords, round, and convert back
|
|
|
|
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
|
|
|
|
|
|
|
|
FakeToScreenX = (Graphics()->ScreenWidth()/(ScreenX1-ScreenX0));
|
|
|
|
FakeToScreenY = (Graphics()->ScreenHeight()/(ScreenY1-ScreenY0));
|
2010-08-16 00:21:18 +00:00
|
|
|
ActualX = (int)(pCursor->m_X * FakeToScreenX);
|
|
|
|
ActualY = (int)(pCursor->m_Y * FakeToScreenY);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
CursorX = ActualX / FakeToScreenX;
|
|
|
|
CursorY = ActualY / FakeToScreenY;
|
|
|
|
|
|
|
|
// same with size
|
2010-08-16 00:21:18 +00:00
|
|
|
ActualSize = (int)(Size * FakeToScreenY);
|
2010-05-29 07:25:38 +00:00
|
|
|
Size = ActualSize / FakeToScreenY;
|
|
|
|
|
|
|
|
// fetch pFont data
|
|
|
|
if(!pFont)
|
|
|
|
pFont = m_pDefaultFont;
|
|
|
|
|
|
|
|
if(!pFont)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pSizeData = GetSize(pFont, ActualSize);
|
|
|
|
RenderSetup(pFont, ActualSize);
|
2010-08-17 22:21:38 +00:00
|
|
|
|
|
|
|
float Scale = 1/pSizeData->m_FontSize;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
// set length
|
|
|
|
if(Length < 0)
|
|
|
|
Length = str_length(pText);
|
|
|
|
|
|
|
|
pEnd = pText + Length;
|
|
|
|
|
|
|
|
// if we don't want to render, we can just skip the first outline pass
|
|
|
|
i = 1;
|
|
|
|
if(pCursor->m_Flags&TEXTFLAG_RENDER)
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
for(;i < 2; i++)
|
|
|
|
{
|
|
|
|
const char *pCurrent = (char *)pText;
|
|
|
|
const char *pEnd = pCurrent+Length;
|
|
|
|
DrawX = CursorX;
|
|
|
|
DrawY = CursorY;
|
2010-10-11 00:29:30 +00:00
|
|
|
LineCount = pCursor->m_LineCount;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
if(pCursor->m_Flags&TEXTFLAG_RENDER)
|
|
|
|
{
|
|
|
|
// TODO: Make this better
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
|
|
if (i == 0)
|
|
|
|
glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[1]);
|
|
|
|
else
|
|
|
|
glBindTexture(GL_TEXTURE_2D, pSizeData->m_aTextures[0]);
|
|
|
|
|
|
|
|
Graphics()->QuadsBegin();
|
|
|
|
if (i == 0)
|
|
|
|
Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.3f*m_TextA);
|
|
|
|
else
|
|
|
|
Graphics()->SetColor(m_TextR, m_TextG, m_TextB, m_TextA);
|
|
|
|
}
|
|
|
|
|
2010-10-11 00:29:30 +00:00
|
|
|
while(pCurrent < pEnd && (pCursor->m_MaxLines < 1 || LineCount <= pCursor->m_MaxLines))
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
int NewLine = 0;
|
|
|
|
const char *pBatchEnd = pEnd;
|
|
|
|
if(pCursor->m_LineWidth > 0 && !(pCursor->m_Flags&TEXTFLAG_STOP_AT_END))
|
|
|
|
{
|
|
|
|
int Wlen = min(WordLength((char *)pCurrent), (int)(pEnd-pCurrent));
|
|
|
|
CTextCursor Compare = *pCursor;
|
|
|
|
Compare.m_X = DrawX;
|
|
|
|
Compare.m_Y = DrawY;
|
|
|
|
Compare.m_Flags &= ~TEXTFLAG_RENDER;
|
|
|
|
Compare.m_LineWidth = -1;
|
|
|
|
TextEx(&Compare, pText, Wlen);
|
|
|
|
|
|
|
|
if(Compare.m_X-DrawX > pCursor->m_LineWidth)
|
|
|
|
{
|
|
|
|
// word can't be fitted in one line, cut it
|
|
|
|
CTextCursor Cutter = *pCursor;
|
|
|
|
Cutter.m_CharCount = 0;
|
|
|
|
Cutter.m_X = DrawX;
|
|
|
|
Cutter.m_Y = DrawY;
|
|
|
|
Cutter.m_Flags &= ~TEXTFLAG_RENDER;
|
|
|
|
Cutter.m_Flags |= TEXTFLAG_STOP_AT_END;
|
|
|
|
|
|
|
|
TextEx(&Cutter, (const char *)pCurrent, Wlen);
|
|
|
|
Wlen = Cutter.m_CharCount;
|
|
|
|
NewLine = 1;
|
|
|
|
|
|
|
|
if(Wlen <= 3) // if we can't place 3 chars of the word on this line, take the next
|
|
|
|
Wlen = 0;
|
|
|
|
}
|
|
|
|
else if(Compare.m_X-pCursor->m_StartX > pCursor->m_LineWidth)
|
|
|
|
{
|
|
|
|
NewLine = 1;
|
|
|
|
Wlen = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
pBatchEnd = pCurrent + Wlen;
|
|
|
|
}
|
|
|
|
|
2011-02-18 10:08:38 +00:00
|
|
|
const char *pTmp = pCurrent;
|
|
|
|
int NextCharacter = str_utf8_decode(&pTmp);
|
2010-05-29 07:25:38 +00:00
|
|
|
while(pCurrent < pBatchEnd)
|
|
|
|
{
|
2011-02-18 10:08:38 +00:00
|
|
|
int Character = NextCharacter;
|
|
|
|
pCurrent = pTmp;
|
|
|
|
NextCharacter = str_utf8_decode(&pTmp);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
if(Character == '\n')
|
|
|
|
{
|
|
|
|
DrawX = pCursor->m_StartX;
|
|
|
|
DrawY += Size;
|
|
|
|
DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign
|
|
|
|
DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY;
|
2010-10-11 00:29:30 +00:00
|
|
|
++LineCount;
|
|
|
|
if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines)
|
|
|
|
break;
|
2010-05-29 07:25:38 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2011-02-18 10:08:38 +00:00
|
|
|
CFontChar *pChr = GetChar(pFont, pSizeData, Character);
|
2010-05-29 07:25:38 +00:00
|
|
|
if(pChr)
|
|
|
|
{
|
2011-02-18 10:08:38 +00:00
|
|
|
float Advance = pChr->m_AdvanceX + Kerning(pFont, Character, NextCharacter)*Scale;
|
2011-01-09 22:25:07 +00:00
|
|
|
if(pCursor->m_Flags&TEXTFLAG_STOP_AT_END && DrawX+Advance*Size-pCursor->m_StartX > pCursor->m_LineWidth)
|
|
|
|
{
|
|
|
|
// we hit the end of the line, no more to render or count
|
|
|
|
pCurrent = pEnd;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(pCursor->m_Flags&TEXTFLAG_RENDER)
|
|
|
|
{
|
|
|
|
Graphics()->QuadsSetSubset(pChr->m_aUvs[0], pChr->m_aUvs[1], pChr->m_aUvs[2], pChr->m_aUvs[3]);
|
|
|
|
IGraphics::CQuadItem QuadItem(DrawX+pChr->m_OffsetX*Size, DrawY+pChr->m_OffsetY*Size, pChr->m_Width*Size, pChr->m_Height*Size);
|
|
|
|
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
|
|
|
}
|
|
|
|
|
2011-02-18 10:08:38 +00:00
|
|
|
DrawX += Advance*Size;
|
|
|
|
pCursor->m_CharCount++;
|
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(NewLine)
|
|
|
|
{
|
|
|
|
DrawX = pCursor->m_StartX;
|
|
|
|
DrawY += Size;
|
|
|
|
GotNewLine = 1;
|
|
|
|
DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign
|
|
|
|
DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY;
|
2010-10-11 00:29:30 +00:00
|
|
|
++LineCount;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(pCursor->m_Flags&TEXTFLAG_RENDER)
|
|
|
|
Graphics()->QuadsEnd();
|
|
|
|
}
|
|
|
|
|
|
|
|
pCursor->m_X = DrawX;
|
2010-10-11 00:29:30 +00:00
|
|
|
pCursor->m_LineCount = LineCount;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
if(GotNewLine)
|
|
|
|
pCursor->m_Y = DrawY;
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
IEngineTextRender *CreateEngineTextRender() { return new CTextRender; }
|