/*
* FreeTypeGX is a wrapper class for libFreeType which renders a compiled
* FreeType parsable font into a GX texture for Wii homebrew development.
* Copyright (C) 2008 Armin Tamzarian
* Modified by Dimok, 2010
*
* This file is part of FreeTypeGX.
*
* FreeTypeGX is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTypeGX 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with FreeTypeGX. If not, see .
*/
#include "FreeTypeGX.h"
using namespace std;
/**
* Convert a short char string to a wide char string.
*
* This routine converts a supplied short character string into a wide character string.
* Note that it is the user's responsibility to clear the returned buffer once it is no longer needed.
*
* @param strChar Character string to be converted.
* @return Wide character representation of supplied character string.
*/
wchar_t* charToWideChar( const char* strChar )
{
if ( !strChar )
return NULL;
wchar_t *strWChar = new ( std::nothrow ) wchar_t[strlen( strChar ) + 1];
if ( !strWChar )
return NULL;
int bt = mbstowcs( strWChar, strChar, strlen( strChar ) );
if ( bt > 0 )
{
strWChar[bt] = 0;
return strWChar;
}
wchar_t *tempDest = strWChar;
while ( ( *tempDest++ = *strChar++ ) );
return strWChar;
}
/**
* Default constructor for the FreeTypeGX class for WiiXplorer.
*/
FreeTypeGX::FreeTypeGX( const uint8_t* fontBuffer, FT_Long bufferSize )
{
ftPointSize = 0;
FT_Init_FreeType( &ftLibrary );
FT_New_Memory_Face( ftLibrary, ( FT_Byte * )fontBuffer, bufferSize, 0, &ftFace );
setVertexFormat( GX_VTXFMT1 );
ftKerningEnabled = FT_HAS_KERNING( ftFace );
}
/**
* Default destructor for the FreeTypeGX class.
*/
FreeTypeGX::~FreeTypeGX()
{
unloadFont();
FT_Done_Face( ftFace );
FT_Done_FreeType( ftLibrary );
}
/**
* Setup the vertex attribute formats for the glyph textures.
*
* This function sets up the vertex format for the glyph texture on the specified vertex format index.
* Note that this function should not need to be called except if the vertex formats are cleared or the specified
* vertex format index is modified.
*
* @param vertexIndex Vertex format index (GX_VTXFMT*) of the glyph textures as defined by the libogc gx.h header file.
*/
void FreeTypeGX::setVertexFormat( uint8_t vertexInd )
{
vertexIndex = vertexInd;
GX_SetVtxAttrFmt( vertexIndex, GX_VA_POS, GX_POS_XYZ, GX_S16, 0 );
GX_SetVtxAttrFmt( vertexIndex, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0 );
GX_SetVtxAttrFmt( vertexIndex, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0 );
}
/**
* Clears all loaded font glyph data.
*
* This routine clears all members of the font map structure and frees all allocated memory back to the system.
*/
void FreeTypeGX::unloadFont()
{
if ( this->fontData.size() == 0 )
return;
map >::iterator itr;
map::iterator itr2;
for ( itr = fontData.begin(); itr != fontData.end(); itr++ )
{
for ( itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++ )
free( itr2->second.glyphDataTexture );
itr->second.clear();
}
fontData.clear();
ftgxAlign.clear();
}
/**
* Caches the given font glyph in the instance font texture buffer.
*
* This routine renders and stores the requested glyph's bitmap and relevant information into its own quickly addressible
* structure within an instance-specific map.
*
* @param charCode The requested glyph's character code.
* @return A pointer to the allocated font structure.
*/
ftgxCharData * FreeTypeGX::cacheGlyphData( wchar_t charCode, int16_t pixelSize )
{
map >::iterator itr;
map::iterator itr2;
itr = fontData.find( pixelSize );
if ( itr != fontData.end() )
{
itr2 = itr->second.find( charCode );
if ( itr2 != itr->second.end() )
{
return &itr2->second;
}
}
FT_UInt gIndex;
uint16_t textureWidth = 0, textureHeight = 0;
if ( ftPointSize != pixelSize )
{
ftPointSize = pixelSize;
FT_Set_Pixel_Sizes( ftFace, 0, ftPointSize );
//!Cache ascender and decender as well
map::iterator itrAlign = ftgxAlign.find( ftPointSize );
if ( itrAlign == ftgxAlign.end() )
{
ftgxAlign[ftPointSize].ascender = ( int16_t ) ftFace->size->metrics.ascender >> 6;
ftgxAlign[ftPointSize].descender = ( int16_t ) ftFace->size->metrics.descender >> 6;
ftgxAlign[ftPointSize].max = 0;
ftgxAlign[ftPointSize].min = 0;
}
}
gIndex = FT_Get_Char_Index( ftFace, ( FT_ULong ) charCode );
if ( gIndex != 0 && FT_Load_Glyph( ftFace, gIndex, FT_LOAD_DEFAULT | FT_LOAD_RENDER ) == 0 )
{
if ( ftFace->glyph->format == FT_GLYPH_FORMAT_BITMAP )
{
FT_Bitmap *glyphBitmap = &ftFace->glyph->bitmap;
textureWidth = glyphBitmap->width + ( 4 - glyphBitmap->width % 4 ) % 4;
textureHeight = glyphBitmap->rows + ( 4 - glyphBitmap->rows % 4 ) % 4;
fontData[pixelSize][charCode].renderOffsetX = ( int16_t ) ftFace->glyph->bitmap_left;
fontData[pixelSize][charCode].glyphAdvanceX = ( uint16_t ) ( ftFace->glyph->advance.x >> 6 );
fontData[pixelSize][charCode].glyphIndex = ( uint32_t ) gIndex;
fontData[pixelSize][charCode].textureWidth = ( uint16_t ) textureWidth;
fontData[pixelSize][charCode].textureHeight = ( uint16_t ) textureHeight;
fontData[pixelSize][charCode].renderOffsetY = ( int16_t ) ftFace->glyph->bitmap_top;
fontData[pixelSize][charCode].renderOffsetMax = ( int16_t ) ftFace->glyph->bitmap_top;
fontData[pixelSize][charCode].renderOffsetMin = ( int16_t ) glyphBitmap->rows - ftFace->glyph->bitmap_top;
fontData[pixelSize][charCode].glyphDataTexture = NULL;
loadGlyphData( glyphBitmap, &fontData[pixelSize][charCode] );
return &fontData[pixelSize][charCode];
}
}
return NULL;
}
/**
* Locates each character in this wrapper's configured font face and proccess them.
*
* This routine locates each character in the configured font face and renders the glyph's bitmap.
* Each bitmap and relevant information is loaded into its own quickly addressible structure within an instance-specific map.
*/
uint16_t FreeTypeGX::cacheGlyphDataComplete( int16_t pixelSize )
{
uint32_t i = 0;
FT_UInt gIndex;
FT_ULong charCode = FT_Get_First_Char( ftFace, &gIndex );
while ( gIndex != 0 )
{
if ( cacheGlyphData( charCode, pixelSize ) != NULL )
++i;
charCode = FT_Get_Next_Char( ftFace, charCode, &gIndex );
}
return ( uint16_t )( i );
}
/**
* Loads the rendered bitmap into the relevant structure's data buffer.
*
* This routine does a simple byte-wise copy of the glyph's rendered 8-bit grayscale bitmap into the structure's buffer.
* Each byte is converted from the bitmap's intensity value into the a uint32_t RGBA value.
*
* @param bmp A pointer to the most recently rendered glyph's bitmap.
* @param charData A pointer to an allocated ftgxCharData structure whose data represent that of the last rendered glyph.
*
*
* Optimized for RGBA8 use by Dimok.
*/
void FreeTypeGX::loadGlyphData( FT_Bitmap *bmp, ftgxCharData *charData )
{
int length = ( ( ( ( charData->textureWidth + 3 ) >> 2 ) * ( ( charData->textureHeight + 3 ) >> 2 ) * 32 * 2 + 31 ) & ~31 );
uint8_t * glyphData = ( uint8_t * ) memalign( 32, length );
if ( !glyphData )
return;
memset( glyphData, 0x00, length );
uint8_t *src = ( uint8_t * )bmp->buffer;
uint32_t offset;
for ( int imagePosY = 0; imagePosY < bmp->rows; ++imagePosY )
{
for ( int imagePosX = 0; imagePosX < bmp->width; ++imagePosX )
{
offset = ( ( ( ( imagePosY >> 2 ) * ( charData->textureWidth >> 2 ) + ( imagePosX >> 2 ) ) << 5 ) + ( ( imagePosY & 3 ) << 2 ) + ( imagePosX & 3 ) ) << 1;
glyphData[offset] = *src;
glyphData[offset+1] = *src;
glyphData[offset+32] = *src;
glyphData[offset+33] = *src;
++src;
}
}
DCFlushRange( glyphData, length );
charData->glyphDataTexture = glyphData;
}
/**
* Determines the x offset of the rendered string.
*
* This routine calculates the x offset of the rendered string based off of a supplied positional format parameter.
*
* @param width Current pixel width of the string.
* @param format Positional format of the string.
*/
int16_t FreeTypeGX::getStyleOffsetWidth( uint16_t width, uint16_t format )
{
if ( format & FTGX_JUSTIFY_LEFT )
return 0;
else if ( format & FTGX_JUSTIFY_CENTER )
return -( width >> 1 );
else if ( format & FTGX_JUSTIFY_RIGHT )
return -width;
return 0;
}
/**
* Determines the y offset of the rendered string.
*
* This routine calculates the y offset of the rendered string based off of a supplied positional format parameter.
*
* @param offset Current pixel offset data of the string.
* @param format Positional format of the string.
*/
int16_t FreeTypeGX::getStyleOffsetHeight( int16_t format, uint16_t pixelSize )
{
map::iterator itrAlign = ftgxAlign.find( pixelSize );
if ( itrAlign == ftgxAlign.end() )
return 0;
switch ( format & FTGX_ALIGN_MASK )
{
case FTGX_ALIGN_TOP:
return itrAlign->second.ascender;
case FTGX_ALIGN_MIDDLE:
default:
return ( itrAlign->second.ascender + itrAlign->second.descender + 1 ) >> 1;
case FTGX_ALIGN_BOTTOM:
return itrAlign->second.descender;
case FTGX_ALIGN_BASELINE:
return 0;
case FTGX_ALIGN_GLYPH_TOP:
return itrAlign->second.max;
case FTGX_ALIGN_GLYPH_MIDDLE:
return ( itrAlign->second.max + itrAlign->second.min + 1 ) >> 1;
case FTGX_ALIGN_GLYPH_BOTTOM:
return itrAlign->second.min;
}
return 0;
}
/**
* Processes the supplied text string and prints the results at the specified coordinates.
*
* This routine processes each character of the supplied text string, loads the relevant preprocessed bitmap buffer,
* a texture from said buffer, and loads the resultant texture into the EFB.
*
* @param x Screen X coordinate at which to output the text.
* @param y Screen Y coordinate at which to output the text. Note that this value corresponds to the text string origin and not the top or bottom of the glyphs.
* @param text NULL terminated string to output.
* @param color Optional color to apply to the text characters. If not specified default value is ftgxWhite: (GXColor){0xff, 0xff, 0xff, 0xff}
* @param textStyle Flags which specify any styling which should be applied to the rendered string.
* @return The number of characters printed.
*/
uint16_t FreeTypeGX::drawText( int16_t x, int16_t y, int16_t z, const wchar_t *text, int16_t pixelSize, GXColor color, uint16_t textStyle, uint16_t textWidth, uint16_t widthLimit )
{
if ( !text )
return 0;
uint16_t fullTextWidth = textWidth > 0 ? textWidth : getWidth( text, pixelSize );
uint16_t x_pos = x, printed = 0;
uint16_t x_offset = 0, y_offset = 0;
GXTexObj glyphTexture;
FT_Vector pairDelta;
if ( textStyle & FTGX_JUSTIFY_MASK )
{
x_offset = getStyleOffsetWidth( fullTextWidth, textStyle );
}
if ( textStyle & FTGX_ALIGN_MASK )
{
y_offset = getStyleOffsetHeight( textStyle, pixelSize );
}
int i = 0;
while ( text[i] )
{
if ( widthLimit > 0 && ( x_pos - x ) > widthLimit )
break;
ftgxCharData* glyphData = cacheGlyphData( text[i], pixelSize );
if ( glyphData != NULL )
{
if ( ftKerningEnabled && i > 0 )
{
FT_Get_Kerning( ftFace, fontData[pixelSize][text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta );
x_pos += pairDelta.x >> 6;
}
GX_InitTexObj( &glyphTexture, glyphData->glyphDataTexture, glyphData->textureWidth, glyphData->textureHeight, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE );
copyTextureToFramebuffer( &glyphTexture, glyphData->textureWidth, glyphData->textureHeight, x_pos + glyphData->renderOffsetX + x_offset, y - glyphData->renderOffsetY + y_offset, z, color );
x_pos += glyphData->glyphAdvanceX;
++printed;
}
++i;
}
if ( textStyle & FTGX_STYLE_MASK )
{
getOffset( text, pixelSize, widthLimit );
drawTextFeature( x + x_offset, y + y_offset, z, pixelSize, fullTextWidth, &ftgxAlign[pixelSize], textStyle, color );
}
return printed;
}
void FreeTypeGX::drawTextFeature( int16_t x, int16_t y, int16_t z, int16_t pixelSize, uint16_t width, ftgxDataOffset *offsetData, uint16_t format, GXColor color )
{
uint16_t featureHeight = pixelSize >> 4 > 0 ? pixelSize >> 4 : 1;
if ( format & FTGX_STYLE_UNDERLINE )
this->copyFeatureToFramebuffer( width, featureHeight, x, y + 1, z, color );
if ( format & FTGX_STYLE_STRIKE )
this->copyFeatureToFramebuffer( width, featureHeight, x, y - ( ( offsetData->max ) >> 1 ), z, color );
}
/**
* Processes the supplied string and return the width of the string in pixels.
*
* This routine processes each character of the supplied text string and calculates the width of the entire string.
* Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function.
*
* @param text NULL terminated string to calculate.
* @return The width of the text string in pixels.
*/
uint16_t FreeTypeGX::getWidth( const wchar_t *text, int16_t pixelSize )
{
if ( !text )
return 0;
uint16_t strWidth = 0;
FT_Vector pairDelta;
int i = 0;
while ( text[i] )
{
ftgxCharData* glyphData = cacheGlyphData( text[i], pixelSize );
if ( glyphData != NULL )
{
if ( ftKerningEnabled && ( i > 0 ) )
{
FT_Get_Kerning( ftFace, fontData[pixelSize][text[i - 1]].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta );
strWidth += pairDelta.x >> 6;
}
strWidth += glyphData->glyphAdvanceX;
}
++i;
}
return strWidth;
}
/**
* Single char width
*/
uint16_t FreeTypeGX::getCharWidth( const wchar_t wChar, int16_t pixelSize, const wchar_t prevChar )
{
uint16_t strWidth = 0;
ftgxCharData * glyphData = cacheGlyphData( wChar, pixelSize );
if ( glyphData != NULL )
{
if ( ftKerningEnabled && prevChar != 0x0000 )
{
FT_Vector pairDelta;
FT_Get_Kerning( ftFace, fontData[pixelSize][prevChar].glyphIndex, glyphData->glyphIndex, FT_KERNING_DEFAULT, &pairDelta );
strWidth += pairDelta.x >> 6;
}
strWidth += glyphData->glyphAdvanceX;
}
return strWidth;
}
/**
* Processes the supplied string and return the height of the string in pixels.
*
* This routine processes each character of the supplied text string and calculates the height of the entire string.
* Note that if precaching of the entire font set is not enabled any uncached glyph will be cached after the call to this function.
*
* @param text NULL terminated string to calculate.
* @return The height of the text string in pixels.
*/
uint16_t FreeTypeGX::getHeight( const wchar_t *text, int16_t pixelSize )
{
getOffset( text, pixelSize );
return ftgxAlign[pixelSize].max - ftgxAlign[pixelSize].min;
}
/**
* Get the maximum offset above and minimum offset below the font origin line.
*
* This function calculates the maximum pixel height above the font origin line and the minimum
* pixel height below the font origin line and returns the values in an addressible structure.
*
* @param text NULL terminated string to calculate.
* @param offset returns the max and min values above and below the font origin line
*
*/
void FreeTypeGX::getOffset( const wchar_t *text, int16_t pixelSize, uint16_t widthLimit )
{
if ( ftgxAlign.find( pixelSize ) != ftgxAlign.end() )
return;
int16_t strMax = 0, strMin = 9999;
uint16_t currWidth = 0;
int i = 0;
while ( text[i] )
{
if ( widthLimit > 0 && currWidth >= widthLimit )
break;
ftgxCharData* glyphData = cacheGlyphData( text[i], pixelSize );
if ( glyphData != NULL )
{
strMax = glyphData->renderOffsetMax > strMax ? glyphData->renderOffsetMax : strMax;
strMin = glyphData->renderOffsetMin < strMin ? glyphData->renderOffsetMin : strMin;
currWidth += glyphData->glyphAdvanceX;
}
++i;
}
if ( ftPointSize != pixelSize )
{
ftPointSize = pixelSize;
FT_Set_Pixel_Sizes( ftFace, 0, ftPointSize );
}
ftgxAlign[pixelSize].ascender = ftFace->size->metrics.ascender >> 6;
ftgxAlign[pixelSize].descender = ftFace->size->metrics.descender >> 6;
ftgxAlign[pixelSize].max = strMax;
ftgxAlign[pixelSize].min = strMin;
}
/**
* Copies the supplied texture quad to the EFB.
*
* This routine uses the in-built GX quad builder functions to define the texture bounds and location on the EFB target.
*
* @param texObj A pointer to the glyph's initialized texture object.
* @param texWidth The pixel width of the texture object.
* @param texHeight The pixel height of the texture object.
* @param screenX The screen X coordinate at which to output the rendered texture.
* @param screenY The screen Y coordinate at which to output the rendered texture.
* @param color Color to apply to the texture.
*/
void FreeTypeGX::copyTextureToFramebuffer( GXTexObj *texObj, f32 texWidth, f32 texHeight, int16_t screenX, int16_t screenY, int16_t screenZ, GXColor color )
{
GX_LoadTexObj( texObj, GX_TEXMAP0 );
GX_InvalidateTexAll();
GX_SetTevOp ( GX_TEVSTAGE0, GX_MODULATE );
GX_SetVtxDesc ( GX_VA_TEX0, GX_DIRECT );
GX_Begin( GX_QUADS, this->vertexIndex, 4 );
GX_Position3s16( screenX, screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_TexCoord2f32( 0.0f, 0.0f );
GX_Position3s16( texWidth + screenX, screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_TexCoord2f32( 1.0f, 0.0f );
GX_Position3s16( texWidth + screenX, texHeight + screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_TexCoord2f32( 1.0f, 1.0f );
GX_Position3s16( screenX, texHeight + screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_TexCoord2f32( 0.0f, 1.0f );
GX_End();
GX_SetTevOp( GX_TEVSTAGE0, GX_PASSCLR );
GX_SetVtxDesc( GX_VA_TEX0, GX_NONE );
}
/**
* Creates a feature quad to the EFB.
*
* This function creates a simple quad for displaying underline or strikeout text styling.
*
* @param featureWidth The pixel width of the quad.
* @param featureHeight The pixel height of the quad.
* @param screenX The screen X coordinate at which to output the quad.
* @param screenY The screen Y coordinate at which to output the quad.
* @param color Color to apply to the texture.
*/
void FreeTypeGX::copyFeatureToFramebuffer( f32 featureWidth, f32 featureHeight, int16_t screenX, int16_t screenY, int16_t screenZ, GXColor color )
{
GX_SetTevOp ( GX_TEVSTAGE0, GX_PASSCLR );
GX_SetVtxDesc ( GX_VA_TEX0, GX_NONE );
GX_Begin( GX_QUADS, this->vertexIndex, 4 );
GX_Position3s16( screenX, screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_Position3s16( featureWidth + screenX, screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_Position3s16( featureWidth + screenX, featureHeight + screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_Position3s16( screenX, featureHeight + screenY, screenZ );
GX_Color4u8( color.r, color.g, color.b, color.a );
GX_End();
GX_SetTevOp( GX_TEVSTAGE0, GX_PASSCLR );
GX_SetVtxDesc( GX_VA_TEX0, GX_NONE );
}