/* vgui_font.cpp - fonts management Copyright (C) 2011 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "vgui_draw.h" #include "vgui_main.h" int FontCache::s_pFontPageSize[FONT_PAGE_SIZE_COUNT] = { 16, 32, 64, 128 }; FontCache::FontCache() : m_CharCache( 0, 256, CacheEntryLessFunc ) { CacheEntry_t listHead = { 0, 0 }; m_LRUListHeadIndex = m_CharCache.Insert( listHead ); m_CharCache[m_LRUListHeadIndex].nextEntry = m_LRUListHeadIndex; m_CharCache[m_LRUListHeadIndex].prevEntry = m_LRUListHeadIndex; for( int i = 0; i < FONT_PAGE_SIZE_COUNT; i++ ) { m_pCurrPage[i] = -1; } } bool FontCache::CacheEntryLessFunc( CacheEntry_t const &lhs, CacheEntry_t const &rhs ) { if( lhs.font < rhs.font ) return true; else if( lhs.font > rhs.font ) return false; return ( lhs.ch < rhs.ch ); } bool FontCache::GetTextureForChar( Font *font, char ch, int *textureID, float **texCoords ) { static CacheEntry_t cacheitem; cacheitem.font = font; cacheitem.ch = ch; Assert( texCoords != NULL ); *texCoords = cacheitem.texCoords; HCacheEntry cacheHandle = m_CharCache.Find( cacheitem ); if( m_CharCache.IsValidIndex( cacheHandle )) { // we have an entry already, return that int page = m_CharCache[cacheHandle].page; *textureID = m_PageList[page].textureID; *texCoords = m_CharCache[cacheHandle].texCoords; return true; } // get the char details int fontTall = font->getTall(); int a, b, c; font->getCharABCwide( (byte)ch, a, b, c ); int fontWide = b; // get a texture to render into int page, drawX, drawY, twide, ttall; if( !AllocatePageForChar( fontWide, fontTall, page, drawX, drawY, twide, ttall )) return false; // create a buffer and render the character into it int nByteCount = s_pFontPageSize[FONT_PAGE_SIZE_COUNT-1] * s_pFontPageSize[FONT_PAGE_SIZE_COUNT-1] * 4; byte * rgba = (byte *)Z_Malloc( nByteCount ); font->getCharRGBA( (byte)ch, 0, 0, fontWide, fontTall, rgba ); // upload the new sub texture VGUI_BindTexture( m_PageList[page].textureID ); VGUI_UploadTextureBlock( m_PageList[page].textureID, drawX, drawY, rgba, fontWide, fontTall ); // set the cache info cacheitem.page = page; cacheitem.texCoords[0] = (float)((double)drawX / ((double)twide)); cacheitem.texCoords[1] = (float)((double)drawY / ((double)ttall)); cacheitem.texCoords[2] = (float)((double)(drawX + fontWide) / (double)twide); cacheitem.texCoords[3] = (float)((double)(drawY + fontTall) / (double)ttall); m_CharCache.Insert( cacheitem ); // return the data *textureID = m_PageList[page].textureID; // memcpy( texCoords, cacheitem.texCoords, sizeof( float ) * 4 ); return true; } int FontCache::ComputePageType( int charTall ) const { for( int i = 0; i < FONT_PAGE_SIZE_COUNT; i++ ) { if( charTall < s_pFontPageSize[i] ) return i; } return -1; } bool FontCache::AllocatePageForChar( int charWide, int charTall, int &pageIndex, int &drawX, int &drawY, int &twide, int &ttall ) { // see if there is room in the last page for this character int nPageType = ComputePageType( charTall ); pageIndex = m_pCurrPage[nPageType]; int nNextX = 0; bool bNeedsNewPage = true; if( pageIndex > -1 ) { Page_t &page = m_PageList[pageIndex]; nNextX = page.nextX + charWide; // make sure we have room on the current line of the texture page if( nNextX > page.wide ) { // move down a line page.nextX = 0; nNextX = charWide; page.nextY += page.fontHeight + 1; } bNeedsNewPage = (( page.nextY + page.fontHeight + 1 ) > page.tall ); } if( bNeedsNewPage ) { // allocate a new page pageIndex = m_PageList.AddToTail(); Page_t &newPage = m_PageList[pageIndex]; m_pCurrPage[nPageType] = pageIndex; newPage.textureID = VGUI_GenerateTexture(); newPage.fontHeight = s_pFontPageSize[nPageType]; newPage.wide = 256; newPage.tall = 256; newPage.nextX = 0; newPage.nextY = 0; nNextX = charWide; // create empty texture VGUI_CreateTexture( newPage.textureID, 256, 256 ); } // output the position Page_t &page = m_PageList[pageIndex]; drawX = page.nextX; drawY = page.nextY; twide = page.wide; ttall = page.tall; // update the next position to draw in page.nextX = nNextX + 1; return true; }