//===== Copyright (c) Valve Corporation, All rights reserved. ======// // // Purpose: PS3-specific font code that for historical reasons is hidden // in the materialsystem. // //==================================================================// #include "pch_materialsystem.h" #ifndef _PS3 #define MATSYS_INTERNAL #endif #include "cmaterialsystem.h" #include "colorspace.h" #include "materialsystem/materialsystem_config.h" #include "materialsystem/imaterialproxyfactory.h" #include "IHardwareConfigInternal.h" #include "shadersystem.h" #include "texturemanager.h" #include "shaderlib/ShaderDLL.h" #include "tier1/callqueue.h" #include "vstdlib/jobthread.h" #include "cmatnullrendercontext.h" #include "datacache/iresourceaccesscontrol.h" #include "filesystem/IQueuedLoader.h" #include "filesystem/IXboxInstaller.h" #include "cdll_int.h" #include #include #include #include "../common/vgui_surfacelib/vguifont.h" #ifdef _PS3 #include "ps3_pathinfo.h" #endif // NOTE: This must be the last file included!!! #include "tier0/memdbgon.h" static const CellFontLibrary *Fontlib(); // in case I make HPS3FONT something other than just a pointer static inline CellFont *CellFontFromHandle( HPS3FONT &h ) { return (CellFont *)h; } wchar_t g_wchFallbackGlyph = L'_'; static inline bool ShouldFallBackToSystemFontForWChar( wchar_t wch, int &nFontTallAdjustment ) { if ( ( wch == 0x00AE /*(R)*/ ) || ( wch == 0x2122 /*(TM)*/ ) ) { nFontTallAdjustment = 1; return true; } return false; } #define TEXTURE_PAGE_HEIGHT 128 // copied from FontTextureCache.cpp, which for some reason hardcodes this. because there's multiple implementations // of the font cache strewn throughout various libraries all linked into the vgui dll, I didn't feel safe changing // it to just use larger textures. class CFallbackFont: public CellFont { protected: int m_nInitError; int m_nTall; CellFontHorizontalLayout m_hlayout; public: CFallbackFont(): m_nInitError(-1), m_nTall( -1 ) { } void Init() { if( m_nInitError == CELL_OK ) { // already initialized return ; } CellFontType fontType; fontType.type = CELL_FONT_TYPE_GOTHIC_JAPANESE_CJK_LATIN_SET; fontType.map = CELL_FONT_MAP_UNICODE; m_nInitError = cellFontOpenFontset( Fontlib(), &fontType, this ); if( m_nInitError == CELL_OK ) { Warning("Cannot open fallback font, error 0x%X\n", m_nInitError ); } } CellFontHorizontalLayout *GetHorizontalLayout() { return &m_hlayout; } int SetTall( int nTall ) { if( m_nInitError != CELL_OK ) { return m_nInitError; } if( nTall == m_nTall ) { return CELL_OK; } float pointSize = nTall * 0.79f; cellFontSetScalePixel( this, pointSize, pointSize ); // sometimes we ask for bigger fonts than the packer can handle. Also, because of accents etc, the font may poke out of the requested tall value. // The packer is for some reason hardcoded to a maximum height of 128. cellFontGetHorizontalLayout( this, &m_hlayout ); if ( m_hlayout.lineHeight + m_hlayout.effectHeight > TEXTURE_PAGE_HEIGHT ) { Warning("FAILSAFE: Fallback Font at '%d' tall is actually %d pixels high, but max texture page is %d. Shrinking to ", nTall, (int)(m_hlayout.lineHeight + m_hlayout.effectHeight), TEXTURE_PAGE_HEIGHT ); do { pointSize *= 0.9; cellFontSetScalePixel( this, nTall, nTall ); cellFontGetHorizontalLayout( this, &m_hlayout ); } while ( ( m_hlayout.lineHeight + m_hlayout.effectHeight > TEXTURE_PAGE_HEIGHT ) && ( pointSize >= 1 ) ); Warning( "%.2f\n", pointSize ); } m_nTall = nTall; return CELL_OK; } void Shutdown() { if( m_nInitError == CELL_OK ) { int nError = cellFontCloseFont( this ); if( nError != CELL_OK ) { Warning("Cannot close fallback font, error 0x%X\n", nError ); } } } }; CFallbackFont g_fallbackFont; void CMaterialSystem::InitializePS3Fonts() { m_vExtantFonts.EnsureCapacity(8); } class CTempFontHandle { public: CMaterialSystem * m_pMaterialSystem; MaterialLock_t m_hLock; CellFont * m_pNewFont; public: CTempFontHandle( CMaterialSystem * pMaterialSystem): m_pMaterialSystem( pMaterialSystem ) { m_hLock = pMaterialSystem->Lock(); m_pNewFont = new CellFont; } CellFont* DetachFont() { CellFont * pFont = m_pNewFont; m_pNewFont = NULL; return pFont; } ~CTempFontHandle() { if( m_pNewFont ) { delete m_pNewFont; } m_pMaterialSystem->Unlock( m_hLock ); } }; HPS3FONT CMaterialSystem::OpenTrueTypeFont( const char *pFontname, int tall, int style ) { CTempFontHandle hTemp( this ); static int sUniquePositiveNumber = 0; // need absolute disk path, look in resource then platform char fontpath[MAX_PATH] = {0}; struct fontTable_t { const char *pFontName; const char *pPath; bool m_bRestrictiveLoadIntoMemory; bool m_bAlwaysLoadIntoMemory; }; int nCellError; if( !V_stricmp( pFontname, "system" ) ) { CellFontType fontType; fontType.type = CELL_FONT_TYPE_GOTHIC_JAPANESE_CJK_LATIN_SET; fontType.map = CELL_FONT_MAP_UNICODE; nCellError = cellFontOpenFontset( Fontlib(), &fontType, hTemp.m_pNewFont ); } else if( !V_stricmp( pFontname, "japanese cjk latin" ) ) { CellFontType fontType; fontType.type = CELL_FONT_TYPE_GOTHIC_JAPANESE_CJK_LATIN_SET; fontType.map = CELL_FONT_MAP_UNICODE; nCellError = cellFontOpenFontset( Fontlib(), &fontType, hTemp.m_pNewFont ); } else { // explicit mapping required, dvd searching too expensive static fontTable_t fontToFilename[] = { #include "shaderapidx9/fontremaptable.h" }; // remap typeface to diskname char const *pDiskname = NULL; for ( int i=0; iGameImagePath(), pDiskname ); } // if we failed to find the font, substitute a system font if ( fontpath[0] == 0 ) { CellFontType fontType; fontType.type = CELL_FONT_TYPE_GOTHIC_JAPANESE_CJK_LATIN_SET; fontType.map = CELL_FONT_MAP_UNICODE; nCellError = cellFontOpenFontset( Fontlib(), &fontType, hTemp.m_pNewFont ); } else { nCellError = cellFontOpenFontFile( Fontlib(), reinterpret_cast(fontpath), 0, ++sUniquePositiveNumber, hTemp.m_pNewFont ); } } CellFont *pNewFont = NULL; if ( nCellError != CELL_FONT_OK ) { Warning( "could not open font '%s': %x\n", pFontname, nCellError ); return false; } else { pNewFont = hTemp.DetachFont(); // do not release the font structure m_vExtantFonts.AddToTail( pNewFont ); } float pointSize = tall * 0.79f; cellFontSetScalePixel( pNewFont, pointSize, pointSize ); // sometimes we ask for bigger fonts than the packer can handle. Also, because of accents etc, the font may poke out of the requested tall value. // The packer is for some reason hardcoded to a maximum height of 128. CellFontHorizontalLayout hlayout; cellFontGetHorizontalLayout( pNewFont, &hlayout ); if ( hlayout.lineHeight + hlayout.effectHeight > TEXTURE_PAGE_HEIGHT ) { Warning("FAILSAFE: %s at '%d' tall is actually %d pixels high, but max texture page is %d. Shrinking to ", pFontname, tall, (int)(hlayout.lineHeight + hlayout.effectHeight), TEXTURE_PAGE_HEIGHT ); do { pointSize *= 0.9; cellFontSetScalePixel( pNewFont, tall, tall ); cellFontGetHorizontalLayout( pNewFont, &hlayout ); } while ( ( hlayout.lineHeight + hlayout.effectHeight > TEXTURE_PAGE_HEIGHT ) && (pointSize >= 1) ); Warning( "%.2f\n", pointSize ); } /* nCellError = cellFontGetHorizontalLayout( &m_sFont, &m_HorizontalLayout ); AssertMsg2( nCellError == CELL_OK, "Could not get horizontal layout for font %s (%x)\n", m_szName.String(), nCellError ); */ /* nCellError = cellFontGetVerticalLayout( &m_sFont, &m_VerticalLayout ); AssertMsg2( nCellError == CELL_OK, "Could not get vertical layout for font %s (%x)\n", m_szName.String(), nCellError ); */ return pNewFont; } void CMaterialSystem::CloseTrueTypeFont( HPS3FONT hFont ) { MaterialLock_t hLock = Lock(); // find the font in the vector CellFont *pFont = CellFontFromHandle( hFont ); cellFontCloseFont( pFont ); if ( m_vExtantFonts.FindAndFastRemove( pFont ) ) { delete pFont; } else { AssertMsg1( false, "Tried to remove a font %x which wasn't in the material system's library\n", hFont ); } Unlock( hLock ); } bool CMaterialSystem::GetTrueTypeFontMetrics( HPS3FONT hFont, int nFallbackTall, wchar_t wchFirst, wchar_t wchLast, CPS3FontMetrics * RESTRICT pFontMetrics, CPS3CharMetrics * RESTRICT pCharMetrics ) { MaterialLock_t hLock = Lock(); int numChars = wchLast - wchFirst + 1; V_memset( pCharMetrics, 0, numChars * sizeof( CPS3CharMetrics ) ); CellFont * RESTRICT pFont = CellFontFromHandle( hFont ); int nCellError = cellFontGetHorizontalLayout( pFont, pFontMetrics ); AssertMsg1( nCellError == CELL_OK, "Could not get horizontal layout for font (%x)\n", nCellError ); // we will still need to go and fill in the max width once we've got the data for all the characters float fMaxWidth = 0; for ( int i = 0; i < numChars; i++ ) { int nMetricsError = CELL_FONT_ERROR_FATAL; int nFallbackFontTallAdjustment = 0; if ( !ShouldFallBackToSystemFontForWChar( wchFirst + i, nFallbackFontTallAdjustment ) ) { nMetricsError = cellFontGetCharGlyphMetrics( pFont, wchFirst + i, pCharMetrics + i ); } if( nMetricsError != CELL_OK ) { // try the fallback font int nFallbackError = g_fallbackFont.SetTall( nFallbackTall + nFallbackFontTallAdjustment ); if( nFallbackError == CELL_OK ) { nMetricsError = cellFontGetCharGlyphMetrics( &g_fallbackFont, wchFirst + i, pCharMetrics + i ); } } if( nMetricsError != CELL_OK ) { nMetricsError = cellFontGetCharGlyphMetrics( pFont, g_wchFallbackGlyph, pCharMetrics + i ); } if ( nMetricsError == CELL_OK ) { fMaxWidth = fmax( fMaxWidth, pCharMetrics[i].Horizontal.advance + 1 ); } else { memset( pCharMetrics+i, 0, sizeof(*pCharMetrics) ); } } pFontMetrics->fMaxWidth = fMaxWidth; Unlock( hLock ); return nCellError == CELL_OK; } // encapsulates the ps3 fontlib renderer class with a RAII interface // so it binds/unbinds automatically class CFontRendererRAII { public: CFontRendererRAII( CellFont *pBoundFont ); ~CFontRendererRAII(); bool IsValid() { return m_nInitSuccess == CELL_FONT_OK; } protected: CellFontRenderer m_Renderer; CellFont *m_pFont; int m_nInitSuccess; }; CFontRendererRAII::CFontRendererRAII( CellFont *pBoundFont ) : m_pFont(NULL) { CellFontRendererConfig config; CellFontRendererConfig_initialize( &config ); // proper setup here m_nInitSuccess = cellFontCreateRenderer( Fontlib(), &config, &m_Renderer ); if ( m_nInitSuccess != CELL_FONT_OK ) { return ; // we have failed. we are failures. the stench of failure hangs upon us. } // connect the renderer to the font m_nInitSuccess = cellFontBindRenderer( pBoundFont, &m_Renderer ); if ( m_nInitSuccess != CELL_FONT_OK ) { return ; // we have failed. we are failures. the stench of failure hangs upon us. } m_pFont = pBoundFont; } CFontRendererRAII::~CFontRendererRAII() { if ( m_pFont ) { cellFontUnbindRenderer( m_pFont ); } cellFontDestroyRenderer( &m_Renderer ); } // OPTIMIZATION OPPORTUNITY: // This can be made to run on the RSX instead of the PPU, if necessary. bool CMaterialSystem::GetTrueTypeGlyphs( HPS3FONT hFont, int nFallbackTall, int numChars, wchar_t *pWch, int *pOffsetX, int *pOffsetY, int * RESTRICT pWidth, int * RESTRICT pHeight, unsigned char *pRGBA, int *pRGBAOffset ) { VPROF( "CMaterialSystem::GetTrueTypeGlyphs" ); MaterialLock_t hLock = Lock(); // Figure out the size of surface/texture we need to allocate int rtWidth = 0; int rtHeight = 0; for ( int i = 0; i < numChars; i++ ) { //rtWidth += pWidth[i]; rtWidth = MAX( rtWidth, pWidth[i] ); rtHeight = MAX( rtHeight, pHeight[i] ); } /* // per resolve() restrictions rtWidth = AlignValue( rtWidth, 32 ); rtHeight = AlignValue( rtHeight, 32 ); */ if ( rtWidth == 0 || rtHeight == 0 ) { AssertMsg2( false, "Tried to draw a %dx%d glyph! What are you trying to pull?!\n", rtWidth, rtHeight ); Unlock( hLock ); return false; } CellFont *pFont = CellFontFromHandle( hFont ); CFontRendererRAII renderer( pFont ), *pFallbackRenderer = NULL; CellFontRenderSurface surface; CellFontGlyphMetrics metrics; CellFontImageTransInfo transInfo; CellFontHorizontalLayout hlayoutSlowWeShouldMoveThisIntoTheFontClass; cellFontRenderSurfaceInit( &surface, NULL, rtWidth, 1, rtWidth, rtHeight ); // the surface doesn't actually use the memory pointer (go figure) cellFontRenderSurfaceSetScissor( &surface, 0, 0, rtWidth, rtHeight ); cellFontGetHorizontalLayout( pFont, &hlayoutSlowWeShouldMoveThisIntoTheFontClass ); const int baselineY = floor(hlayoutSlowWeShouldMoveThisIntoTheFontClass.baseLineY); // struct XUIRect { int x, int y, int w, int h }; // Draw the characters, stepping across the texture // int xCursor = 0; unsigned int successfulWrites = 0; for ( int i = 0; i < numChars; i++) { /* // FIXME: the drawRect params don't make much sense (should use "(xCursor+pWidth[i]), pHeight[i]", but then some characters disappear!) XUIRect drawRect = XUIRect( xCursor + pOffsetX[i], pOffsetY[i], rtWidth, rtHeight ); cellFontRenderSurfaceSetScissor( &surface, drawRect.x, drawRect.y, drawRect.w, drawRect.h ); wchar_t text[2] = { pWch[i], 0 }; XuiDrawText( m_hDC, text, XUI_FONT_STYLE_NORMAL|XUI_FONT_STYLE_SINGLE_LINE|XUI_FONT_STYLE_NO_WORDWRAP, 0, &drawRect ); xCursor += pWidth[i]; */ // draw one glyph to the surface int ps3suc = CELL_FONT_ERROR_FATAL; int nFallbackFontTallAdjustment = 0; if ( !ShouldFallBackToSystemFontForWChar( pWch[i], nFallbackFontTallAdjustment ) ) { ps3suc = cellFontRenderCharGlyphImage( pFont, pWch[i], &surface, 0, 0, &metrics, &transInfo ); } if ( ps3suc != CELL_OK ) { if( CELL_OK == g_fallbackFont.SetTall( nFallbackTall + nFallbackFontTallAdjustment ) ) { if( !pFallbackRenderer ) pFallbackRenderer = new CFontRendererRAII( &g_fallbackFont ); ps3suc = cellFontRenderCharGlyphImage( &g_fallbackFont, pWch[i], &surface, 0, 0, &metrics, &transInfo ); } } if( ps3suc != CELL_OK ) { ps3suc = cellFontRenderCharGlyphImage( pFont, g_wchFallbackGlyph, &surface, 0, 0, &metrics, &transInfo ); } if ( ps3suc == CELL_OK ) { // blit the glyph to the bitmap const int ibw = transInfo.imageWidthByte; const int iBearingY = ceilf(metrics.Horizontal.bearingY); // start the conversion early to avoid a LHS int destPitch = pWidth[i]*4; // distance between one row and the next IN BYTES unsigned char *pLinear = pRGBA + pRGBAOffset[i]; // Clear out all pixels to black, since the source glyph size may not match the destination buffer size perfectly memset( pLinear, 0, pHeight[i] * destPitch ); if ( transInfo.imageHeight==0 || transInfo.imageWidth==0 ) { // this can happen for space or control characters // just blank out the destination box continue; // bail out and go to next character ! } // work out the size of the region to copy -- which is the smaller of the source or destination buffer. // in theory these two should be identical, but the PS3 OS tends to overallocate space slightly // in its render surface, and sometimes the VGUI font code is expecting a blank column of pixels // where the PS3 generates none. // also we'll try to align the font glyph to the bottom of the destination rect, rather than // the top. int numLeftPadPixels = ( int )floorf( metrics.Horizontal.bearingX ); numLeftPadPixels = MAX( 0, numLeftPadPixels ); int iteratorMaxColumn = MIN( pWidth[i] - numLeftPadPixels, transInfo.imageWidth ); int iteratorMaxRow = MIN( pHeight[i], transInfo.imageHeight ); unsigned char *pLinearAligned = pLinear; // work out where to start writing into the image so that chars are aligned to bottom of rect pLinearAligned = pLinear + MAX(0, baselineY-iBearingY)*destPitch; // for ( int row = 0 ; row < transInfo.imageHeight ; ++row ) for ( int row = 0 ; row < iteratorMaxRow; ++row ) { uint32 * pRowStart = reinterpret_cast(pLinearAligned + (row*destPitch)); // we will write the RGBA one word at a time for ( int col = 0 ; col < iteratorMaxColumn; ++col ) { // white characters; only the alpha mask is copied from the rendered glyph // exception: totally absent regions are marked as just black uint8 a = transInfo.Image[ row * ibw + col ]; *(pRowStart + col + numLeftPadPixels) = a ? 0xFFFFFF00 | a : 0 ; } } #if 0 if ( FILE *f = fopen( "/app_home/dbgfonts.txt", "a+" ) ) { fprintf( f, "% 4d %C [%dx%d]\n", pWch[i], pWch[i], transInfo.imageWidth, transInfo.imageHeight ); fprintf( f, " [%d]\n", pWidth[i] ); fprintf( f, " %0.5f %0.5f %0.5f\n", metrics.Horizontal.bearingX, metrics.width, metrics.Horizontal.advance ); for ( int row = 0; row < MAX( MAX( pHeight[i], ceilf( metrics.height ) ), transInfo.imageHeight ); ++ row ) { for ( int col = 0; col < MAX( pWidth[i], ceilf( metrics.Horizontal.advance ) ); ++ col ) { uint8 a = transInfo.Image[ row * ibw + col ]; fprintf( f, a ? ( (a>0x20) ? "X" : "*" ) : " " ); } fprintf( f, "|\n" ); } fprintf( f, "-----------------------------\n" ); fclose(f); } #endif #if 0 // draw a line around the outer border for debugging for ( int row = 0 ; row < pHeight[i]; ++row ) { *(reinterpret_cast(pLinear + (row*destPitch))+0) = 0xFFFFFFFF; *(reinterpret_cast(pLinear + (row*destPitch))+pWidth[i]-1) = 0xFFFFFFFF; } // top and bottom row memset( pLinear, 0xFF, pWidth[i] * 4 ); memset( (pLinear + ((pHeight[i]-1)*destPitch)), 0xFF, pWidth[i] * 4 ); #endif successfulWrites++; } else { AssertMsg2( false, "Couldn't draw u'%x', err %x\n", (unsigned int) *(pWch+i), ps3suc ); } } if( pFallbackRenderer ) delete pFallbackRenderer; Unlock( hLock ); return successfulWrites > 0; } static int load_libfont_module() { int ret; ret = cellSysmoduleLoadModule( CELL_SYSMODULE_FONT ); if ( ret == CELL_OK ) { ret = cellSysmoduleLoadModule( CELL_SYSMODULE_FREETYPE_TT ); if ( ret == CELL_OK ) { ret = cellSysmoduleLoadModule( CELL_SYSMODULE_FONTFT ); if ( ret == CELL_OK ) { return CELL_OK; // Success } // Error handling as follows (Unload all loads) cellSysmoduleUnloadModule( CELL_SYSMODULE_FREETYPE_TT ); } cellSysmoduleUnloadModule( CELL_SYSMODULE_FONT ); } return ret; // Error end } static void* fonts_malloc( void*obj, uint32_t size ) { (void)obj; return malloc( size ); } static void fonts_free( void*obj, void*p ) { (void)obj; free( p ); } static void* fonts_realloc( void*obj, void* p, uint32_t size ) { (void)obj; return realloc( p, size ); } static void* fonts_calloc( void*obj, uint32_t numb, uint32_t blockSize ) { (void)obj; return calloc( numb, blockSize ); } static int s_FontLibraryRefCount = 0; static uint32 *s_FontFileCache = NULL; static const CellFontLibrary *s_FontLibrary = NULL; static CellFontEntry *s_UserFontEntries = NULL; bool CMaterialSystem::PS3InitFontLibrary( unsigned fontFileCacheSizeInBytes, unsigned maxNumFonts ) { if ( s_FontLibraryRefCount > 0 ) { // don't need to load the font library, just add one to refcount ++s_FontLibraryRefCount; return true; } else if ( s_FontLibraryRefCount < 0 ) { Error( "Font library refcount is %d!\n", s_FontLibraryRefCount ); } else { ++s_FontLibraryRefCount; } VPROF("CMaterialSystem::PS3InitFontLibrary"); if ( s_FontFileCache ) { Warning("Tried to init font library twice without intervening unload\n"); return false; } int ret; if ( (ret=load_libfont_module()) != CELL_OK ) { Warning("Could not load font library: %x\n", ret); } maxNumFonts = maxNumFonts ? maxNumFonts : 1; if (s_FontFileCache) { Assert(false); // wasn't cleaned up?! delete[] s_FontFileCache; } if (s_UserFontEntries) { Assert(false); // wasn't cleaned up?! delete[] s_UserFontEntries; } s_FontFileCache = new uint32[fontFileCacheSizeInBytes >> 2]; s_UserFontEntries = new CellFontEntry[maxNumFonts]; CellFontConfig config; config.FileCache.buffer = s_FontFileCache; config.FileCache.size = fontFileCacheSizeInBytes; config.userFontEntrys = s_UserFontEntries; config.userFontEntryMax = maxNumFonts; ret = cellFontInit(&config); if ( ret == CELL_OK ) { // can override malloc/free here: CellFontLibraryConfigFT ftConfig; CellFontLibraryConfigFT_initialize( &ftConfig ); ftConfig.MemoryIF.Object = NULL; ftConfig.MemoryIF.Malloc = fonts_malloc; ftConfig.MemoryIF.Free = fonts_free; ftConfig.MemoryIF.Realloc = fonts_realloc; ftConfig.MemoryIF.Calloc = fonts_calloc; ret = cellFontInitLibraryFreeType( &ftConfig, &s_FontLibrary ); if ( ret == CELL_OK ) { g_fallbackFont.Init( ); return true; } else { Warning( "cellFontInitLibraryFreeType failed, %x\n", ret ); return false; } } else { Warning( "CellFontInit failed, %x\n", ret ); return false; } } void *CMaterialSystem::PS3GetFontLibPtr() { return (void*)s_FontLibrary; } void CMaterialSystem::PS3DumpFontLibrary() { if ( s_FontLibraryRefCount <= 0 ) { Warning("Font library refcount is %d during unload!\n", s_FontLibraryRefCount); return; } else if ( s_FontLibrary == NULL ) { Warning("\t!!!FAILSAFE!!!\nPS3 font library refcount %d but was somehow unloaded!\n", s_FontLibraryRefCount); s_FontLibraryRefCount = 0; } else if ( --s_FontLibraryRefCount > 0 ) { // refcount is still greater than one, don't need to unload return; } // Warning( "Font library was dumped with %d fonts left open.\n", m_vExtantFonts.Count() ); if ( s_FontLibrary ) { g_fallbackFont.Shutdown(); cellFontEndLibrary( s_FontLibrary ); cellFontEnd(); delete[] s_FontFileCache; s_FontFileCache = NULL; // handled by cellFontEndLibrary: // delete s_FontLibrary; s_FontLibrary = NULL; delete[] s_UserFontEntries; s_UserFontEntries = NULL; cellSysmoduleUnloadModule( CELL_SYSMODULE_FONTFT ); cellSysmoduleUnloadModule( CELL_SYSMODULE_FREETYPE_TT ); cellSysmoduleUnloadModule( CELL_SYSMODULE_FONT ); } } static const CellFontLibrary *Fontlib() { return s_FontLibrary; }