Lazy Foo' Productions


Transforming Text

Transforming Text screenshot

Last Updated: Aug 9th, 2012

Text as far as we're concerned is just a set of textured quads. If we treat a rendered string as one big quad, we can transform it just like any quad.
From LFont.cpp
        LFRect getStringArea( std::string text );
        /*
        Pre Condition:
         -A loaded font
        Post Condition:
         -Returns area for given text
        Side Effects:
         -None
        */
The new LFont function getStringArea() gives us the rendering area for an entire string.
From LFont.cpp
LFRect LFont::getStringArea( std::string text )
{
    //Initialize area
    GLfloat subWidth = 0.f;
    LFRect area = { 0.f, 0.f, subWidth, mLineHeight };

    //Go through string
    for( int i = 0; i < text.length(); ++i )
    {
        //Space
        if( text[ i ] == ' ' )
        {
            subWidth += mSpace;
        }
        //Newline
        else if( text[ i ] == '\n' )
        {
            //Add another line
            area.h += mLineHeight;

            //Check for max width
            if( subWidth > area.w )
            {
                area.w = subWidth;
                subWidth = 0.f;
            }
        }
        //Character
        else
        {
            //Get ASCII
            GLuint ascii = (unsigned char)text[ i ];
            subWidth += mClips[ ascii ].w;
        }
    }

    //Check for max width
    if( subWidth > area.w )
    {
        area.w = subWidth;
    }

    return area;
}
The way this function calculates area is simple. For every newline, it adds on a line height to the area height. To calculate how wide the text area is, we need to find the line of text with the greatest width.

To do that, we go through the string adding the character/space width for each character to "subWidth". When we reach a newline or the end of the whole string, we check if this line of text has a greater width than the current text area width. If it is, it means we found the greatest line width for the string.
From LUtil.cpp
//Font
LFont gFont;

//Text areas
LFRect gScaledArea = { 0.f, 0.f, 0.f, 0.f };
LFRect gPivotArea = { 0.f, 0.f, 0.f, 0.f };
LFRect gCirclingArea = { 0.f, 0.f, 0.f, 0.f };

//Transformation variables
GLfloat gBigTextScale = 3.f;
GLfloat gPivotAngle = 0.f;
GLfloat gCirclingAngle = 0.f;
At the top of LUtil.cpp we declare the font, the text areas for the text we're going to render, and various transformation variables.
From LUtil.cpp
bool loadMedia()
{
    //Load text
    if( !gFont.loadFreeType( "25_transforming_text/lazy.ttf", 60 ) )
    {
        printf( "Unable to load ttf font!\n" );
        return false;
    }

    //Calculate rendering areas
    gScaledArea = gFont.getStringArea( "Big Text!" );
    gPivotArea = gFont.getStringArea( "Pivot" );
    gCirclingArea = gFont.getStringArea( "Wheee!" );

    return true;
}
After we load the text, we precalculate the render areas for all of the strings so we don't calculate them every time we render.
From LUtil.cpp
void update()
{
    //Update angles
    gPivotAngle += -1.f;
    gCirclingAngle += +2.f;

    //Scale
    gBigTextScale += 0.1f;
    if( gBigTextScale >= 3.f )
    {
        gBigTextScale = 0.f;
    }
}
In the update function() we update rotation angles and text scaling.
From LUtil.cpp
void render()
{
    //Clear color buffer
    glClear( GL_COLOR_BUFFER_BIT );


    //Big upper middle text
    glLoadIdentity();
    glColor3f( 1.f, 0.f, 0.f );

    //Move to render point
    glTranslatef( ( SCREEN_WIDTH - gScaledArea.w * gBigTextScale ) / 2.f, ( SCREEN_HEIGHT - gScaledArea.h * gBigTextScale ) / 4.f, 0.f );

    //Scale and render
    glScalef( gBigTextScale, gBigTextScale, gBigTextScale );
    gFont.renderText( 0.f, 0.f, "Big Text!" , &gScaledArea, LFONT_TEXT_ALIGN_CENTERED_H );
First we render some red text that pops out at the user. First we want to translate the text to the center of the top half of the screen. Since we're translating a scaled quad (we're treating the text area like a single quad) in unscaled coordinates, we scale the text area in the call to glTranslate().

Then we call glScale() and render the scaled text.
From LUtil.cpp
    //Lower pivoting text
    glLoadIdentity();
    glColor3f( 0.f, 1.f, 0.f );

    //Move to render point
    glTranslatef( ( SCREEN_WIDTH - gPivotArea.w * 1.5f ) / 2.f, ( SCREEN_HEIGHT - gPivotArea.h * 1.5f ) * 3.f / 4.f, 0.f );

    //Scale and move to pivot point
    glScalef( 1.5f, 1.5f, 1.5f );
    glTranslatef( gPivotArea.w / 2.f, gPivotArea.h / 2.f, 0.f );

    //Rotate around pivot
    glRotatef( gPivotAngle, 0.f, 0.f, 1.f );

    //Move back to render point and render
    glTranslatef( -gPivotArea.w / 2.f, -gPivotArea.h / 2.f, 0.f );
    gFont.renderText( 0.f, 0.f, "Pivot", &gPivotArea, LFONT_TEXT_ALIGN_CENTERED_H );
Here we have rotating green text that rotates around it's center at the bottom half of the screen.
From LUtil.cpp
    //Circling text
    glLoadIdentity();
    glColor3f( 0.f, 0.f, 1.f );

    //Move to center of screen
    glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );

    //Rotate around center
    glRotatef( gCirclingAngle, 0.f, 0.f, 1.f );

    //Move to arm position
    glTranslatef( 0.f, -SCREEN_HEIGHT / 2.f, 0.f );

    //Center on arm
    glTranslatef( -gCirclingArea.w / 2.f, 0.f, 0.f );

    //Render
    gFont.renderText( 0.f, 0.f, "Wheee!", &gCirclingArea, LFONT_TEXT_ALIGN_CENTERED_H );


    //Update screen
    glutSwapBuffers();
}
Finally we render text that circles around the screen. It works by rotating around the center of the screen.