/* (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. */ #include #include #include #include #ifdef CONF_FAMILY_WINDOWS #include #endif // ft2 texture #include #include FT_FREETYPE_H // TODO: Refactor: clean this up enum { MAX_CHARACTERS = 64, }; static int aFontSizes[] = {8,9,10,11,12,13,14,15,16,17,18,19,20,36,64}; #define NUM_FONT_SIZES (sizeof(aFontSizes)/sizeof(int)) struct CFontChar { int m_ID; // 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; int m_aTextures[2]; 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; }; class CFont { public: char m_aFilename[512]; 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; float m_TextOutlineR; float m_TextOutlineG; float m_TextOutlineB; float m_TextOutlineA; //int m_FontTextureFormat; CFont *m_pDefaultFont; 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); for(int i = 0; i < 2; i++) { if(pSizeData->m_aTextures[i] != 0) { Graphics()->UnloadTexture(pSizeData->m_aTextures[i]); FontMemoryUsage -= pSizeData->m_TextureWidth*pSizeData->m_TextureHeight; pSizeData->m_aTextures[i] = 0; } pSizeData->m_aTextures[i] = Graphics()->LoadTextureRaw(Width, Height, CImageInfo::FORMAT_ALPHA, pMem, CImageInfo::FORMAT_ALPHA, IGraphics::TEXLOAD_NOMIPMAPS); FontMemoryUsage += Width*Height; } pSizeData->m_NumXChars = Xchars; pSizeData->m_NumYChars = Ychars; pSizeData->m_TextureWidth = Width; pSizeData->m_TextureHeight = Height; pSizeData->m_CurrentCharacter = 0; dbg_msg("text", "pFont memory usage: %d", FontMemoryUsage); mem_free(pMem); } int AdjustOutlineThicknessToFontSize(int OutlineThickness, int FontSize) { if(FontSize > 36) OutlineThickness *= 4; else if(FontSize >= 18) OutlineThickness *= 2; return OutlineThickness; } 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); int OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize); { 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]; } void UploadGlyph(CFontSizeData *pSizeData, int Texnum, int SlotID, int Chr, const void *pData) { int x = (SlotID%pSizeData->m_NumXChars) * (pSizeData->m_TextureWidth/pSizeData->m_NumXChars); int y = (SlotID/pSizeData->m_NumXChars) * (pSizeData->m_TextureHeight/pSizeData->m_NumYChars); Graphics()->LoadTextureRawSub(pSizeData->m_aTextures[Texnum], x, y, pSizeData->m_TextureWidth/pSizeData->m_NumXChars, pSizeData->m_TextureHeight/pSizeData->m_NumYChars, CImageInfo::FORMAT_ALPHA, pData); /* 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);*/ } // 32k of data used for rendering glyphs unsigned char ms_aGlyphData[(1024/8) * (1024/8)]; unsigned char ms_aGlyphDataOutlined[(1024/8) * (1024/8)]; 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() && (pSizeData->m_NumXChars < MAX_CHARACTERS || pSizeData->m_NumYChars < MAX_CHARACTERS)) { IncreaseTextureSize(pSizeData); return GetSlot(pSizeData); } return Oldest; } } int RenderGlyph(CFont *pFont, CFontSizeData *pSizeData, int Chr) { FT_Bitmap *pBitmap; int SlotID = 0; 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; unsigned 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 SlotID = GetSlot(pSizeData); if(SlotID < 0) return -1; // adjust spacing int OutlineThickness = AdjustOutlineThicknessToFontSize(1, pSizeData->m_FontSize); 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 < (unsigned)pBitmap->rows; py++) // ignore_convention for(px = 0; px < (unsigned)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 < (unsigned)pBitmap->rows; py++) // ignore_convention for(px = 0; px < (unsigned)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; (int)py < SlotW; py++) for(px = 0; (int)px < SlotH; px++) ms_aGlyphData[py*SlotW+px] = 255; // upload the glyph UploadGlyph(pSizeData, 0, SlotID, Chr, ms_aGlyphData); if(OutlineThickness == 1) { Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH); UploadGlyph(pSizeData, 1, SlotID, Chr, ms_aGlyphDataOutlined); } else { for(int i = OutlineThickness; i > 0; i-=2) { Grow(ms_aGlyphData, ms_aGlyphDataOutlined, SlotW, SlotH); Grow(ms_aGlyphDataOutlined, ms_aGlyphData, SlotW, SlotH); } UploadGlyph(pSizeData, 1, SlotID, Chr, ms_aGlyphData); } // set char info { CFontChar *pFontchr = &pSizeData->m_aCharacters[SlotID]; 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 pFontchr->m_ID = Chr; 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 pFontchr->m_aUvs[0] = (SlotID%pSizeData->m_NumXChars) / (float)(pSizeData->m_NumXChars); pFontchr->m_aUvs[1] = (SlotID/pSizeData->m_NumXChars) / (float)(pSizeData->m_NumYChars); pFontchr->m_aUvs[2] = pFontchr->m_aUvs[0] + Width*Uscale; pFontchr->m_aUvs[3] = pFontchr->m_aUvs[1] + Height*Vscale; } return SlotID; } 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++) { if(pSizeData->m_aCharacters[i].m_ID == Chr) { 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.0f; m_TextG = 1.0f; m_TextB = 1.0f; m_TextA = 1.0f; m_TextOutlineR = 0.0f; m_TextOutlineG = 0.0f; m_TextOutlineB = 0.0f; m_TextOutlineA = 0.3f; m_pDefaultFont = 0; m_FTLibrary = 0; // GL_LUMINANCE can be good for debugging //m_FontTextureFormat = GL_ALPHA; } virtual ~CTextRender() { if(m_pDefaultFont != 0) DestroyFont(m_pDefaultFont); if(m_FTLibrary != 0) FT_Done_FreeType(m_FTLibrary); } virtual void Init() { m_pGraphics = Kernel()->RequestInterface(); FT_Init_FreeType(&m_FTLibrary); } virtual CFont *LoadFont(const char *pFilename) { CFont *pFont = (CFont *)mem_alloc(sizeof(CFont), 1); mem_zero(pFont, sizeof(CFont)); 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) { FT_Done_Face(pFont->m_FtFace); mem_free(pFont); } virtual void SetDefaultFont(CFont *pFont) { dbg_msg("textrender", "default pFont set %p", pFont); if(m_pDefaultFont != 0) DestroyFont(m_pDefaultFont); 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; } virtual int TextLineCount(void *pFontSetV, float Size, const char *pText, float LineWidth) { 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 TextOutlineColor(float r, float g, float b, float a) { m_TextOutlineR = r; m_TextOutlineG = g; m_TextOutlineB = b; m_TextOutlineA = 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; float DrawX = 0.0f, DrawY = 0.0f; int LineCount = 0; float CursorX, CursorY; 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)); ActualX = (int)(pCursor->m_X * FakeToScreenX); ActualY = (int)(pCursor->m_Y * FakeToScreenY); CursorX = ActualX / FakeToScreenX; CursorY = ActualY / FakeToScreenY; // same with size ActualSize = (int)(Size * FakeToScreenY); Size = ActualSize / FakeToScreenY; // fetch pFont data if(!pFont) pFont = m_pDefaultFont; if(!pFont) return; pSizeData = GetSize(pFont, ActualSize); RenderSetup(pFont, ActualSize); float Scale = 1/pSizeData->m_FontSize; // set length if(Length < 0) Length = str_length(pText); // 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; LineCount = pCursor->m_LineCount; if(pCursor->m_Flags&TEXTFLAG_RENDER) { // TODO: Make this better if (i == 0) Graphics()->TextureSet(pSizeData->m_aTextures[1]); else Graphics()->TextureSet(pSizeData->m_aTextures[0]); Graphics()->QuadsBegin(); if (i == 0) Graphics()->SetColor(m_TextOutlineR, m_TextOutlineG, m_TextOutlineB, m_TextOutlineA*m_TextA); else Graphics()->SetColor(m_TextR, m_TextG, m_TextB, m_TextA); } while(pCurrent < pEnd && (pCursor->m_MaxLines < 1 || LineCount <= pCursor->m_MaxLines)) { 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, pCurrent, 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; } const char *pTmp = pCurrent; int NextCharacter = str_utf8_decode(&pTmp); while(pCurrent < pBatchEnd) { int Character = NextCharacter; pCurrent = pTmp; NextCharacter = str_utf8_decode(&pTmp); if(Character == '\n') { DrawX = pCursor->m_StartX; DrawY += Size; DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; ++LineCount; if(pCursor->m_MaxLines > 0 && LineCount > pCursor->m_MaxLines) break; continue; } CFontChar *pChr = GetChar(pFont, pSizeData, Character); if(pChr) { float Advance = pChr->m_AdvanceX + Kerning(pFont, Character, NextCharacter)*Scale; 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; } 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); } DrawX += Advance*Size; pCursor->m_CharCount++; } } if(NewLine) { DrawX = pCursor->m_StartX; DrawY += Size; GotNewLine = 1; DrawX = (int)(DrawX * FakeToScreenX) / FakeToScreenX; // realign DrawY = (int)(DrawY * FakeToScreenY) / FakeToScreenY; ++LineCount; } } if(pCursor->m_Flags&TEXTFLAG_RENDER) Graphics()->QuadsEnd(); } pCursor->m_X = DrawX; pCursor->m_LineCount = LineCount; if(GotNewLine) pCursor->m_Y = DrawY; } virtual void UploadText(int TextureID, const char *pText, int Length, float x, float y, int Size, int MaxWidth, int MaxSize = -1, int MinSize = -1) { CFont *pFont = m_pDefaultFont; FT_Bitmap *pBitmap; if(!pFont) return; // set length if(Length < 0) Length = str_length(pText); const char *pCurrent = (char *)pText; const char *pEnd = pCurrent+Length; int WidthLastChars = 0; int FontSize = Size; //adjust font size by the full space if(Size == -1) { bool FoundMaxFontSize = false; if(MinSize == -1) MinSize = 8; FontSize = MinSize; while(!FoundMaxFontSize) { int WidthOfText = 0; while(pCurrent < pEnd) { const char *pTmp = pCurrent; int NextCharacter = str_utf8_decode(&pTmp); if(NextCharacter) { FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, FontSize); if(FT_Load_Char(pFont->m_FtFace, NextCharacter, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) { dbg_msg("pFont", "error loading glyph %d", NextCharacter); pCurrent = pTmp; continue; } pBitmap = &pFont->m_FtFace->glyph->bitmap; WidthOfText += pBitmap->width + 1; } pCurrent = pTmp; } if(WidthOfText > MaxWidth || (MaxSize != -1 && FontSize > MaxSize)) FoundMaxFontSize = true; pCurrent = (char *)pText; pEnd = pCurrent+Length; ++FontSize; } } while(pCurrent < pEnd) { const char *pTmp = pCurrent; int NextCharacter = str_utf8_decode(&pTmp); if(NextCharacter) { unsigned int px, py; FT_Set_Pixel_Sizes(pFont->m_FtFace, 0, FontSize-1); if(FT_Load_Char(pFont->m_FtFace, NextCharacter, FT_LOAD_RENDER|FT_LOAD_NO_BITMAP)) { dbg_msg("pFont", "error loading glyph %d", NextCharacter); pCurrent = pTmp; continue; } pBitmap = &pFont->m_FtFace->glyph->bitmap; // ignore_convention int MaxSize = (MaxWidth - WidthLastChars); if (MaxSize > 0) { int SlotW = ((unsigned int)MaxSize < pBitmap->width ? MaxSize : pBitmap->width); int SlotH = pBitmap->rows; int SlotSize = SlotW*SlotH; // prepare glyph data mem_zero(ms_aGlyphData, SlotSize); if(pBitmap->pixel_mode == FT_PIXEL_MODE_GRAY) // ignore_convention { for (py = 0; py < (unsigned)SlotH; py++) // ignore_convention for (px = 0; px < (unsigned)SlotW; px++) { ms_aGlyphData[(py)*SlotW + px] = pBitmap->buffer[py*pBitmap->width + px]; // ignore_convention } } /*else if(pBitmap->pixel_mode == FT_PIXEL_MODE_MONO) // ignore_convention { for(py = 0; py < (unsigned)pBitmap->rows; py++) // ignore_convention for(px = 0; px < (unsigned)pBitmap->width; px++) // ignore_convention { if(pBitmap->buffer[py*pBitmap->pitch+px/8]&(1<<(7-(px%8)))) // ignore_convention ms_aGlyphData[(py)*SlotW+px] = 255; } }*/ Graphics()->LoadTextureRawSub(TextureID, x + WidthLastChars, y, SlotW, SlotH, CImageInfo::FORMAT_ALPHA, ms_aGlyphData); WidthLastChars += (SlotW + 1); } } pCurrent = pTmp; } } }; IEngineTextRender *CreateEngineTextRender() { return new CTextRender; }