Making fonts in GLFW

uthegental wrote on Sunday, August 24, 2008:

I have read in this forum that it is possible to modify GLFW to use fonts under windows. Could somone tell me how to do that?

nexekho wrote on Thursday, May 14, 2009:

I have a fairly simple function that generates 256 display lists, one for each character, from a 512x512 texture automatically doing crude spacing. It’s currently hardcoded to interface/font.tga but it’s pretty easy to use. You interested?

[code]
uint8_t ui8CharacterOffsetA[ 256 ];
uint8_t ui8CharacterOffsetB[ 256 ];
uint8_t ui8CharacterWidth[ 256 ];

GLuint gluiFont = 0;
GLuint gluiCharacter[ 256 ];

void RenderSingleLineTextRight( char * cText, uint8_t ui8Characters, uint16_t ui16Width );
void RenderSingleLineTextCentred( char * cText, uint8_t ui8Characters, uint16_t ui16Width );
void RenderSingleLineText( char * cText, uint8_t ui8Characters, uint16_t ui16Width );
void RenderText( char * cText, uint8_t ui8Characters );

uint8_t InitFont( void );
void TerminateFont( void );
[/code]

[code]
void RenderSingleLineTextRight( char * cText, uint8_t ui8Characters, uint16_t ui16Width )
{

glPushMatrix\(\);

    glBindTexture\( GL\_TEXTURE\_2D, gluiFont \);

    uint8\_t ui8MyCharacter = ui8Characters - 1;

    uint8\_t ui8EscapeWidth = \( ui8CharacterWidth\[ 46 \] \* 3 \) + 6;
    uint16\_t ui16Progress = 0;
    uint8\_t ui8CharactersToDraw = ui8Characters;

    while\( ui8MyCharacter < UINT8\_MAX \)
    \{

        if\( \( ui16Width - ui16Progress \) - ui8CharacterWidth\[ \( uint8\_t \) cText\[ ui8MyCharacter \] \] < ui8EscapeWidth \)
        \{

            ui16Progress += ui8EscapeWidth;

            ui8MyCharacter = UINT8\_MAX;

        \}
        else
        \{

            ui16Progress += ui8CharacterWidth\[ \( uint8\_t \) cText\[ ui8MyCharacter \] \] + 2;

            ui8MyCharacter--;
            ui8CharactersToDraw--;

        \}

    \}

    glTranslatef\( ui16Width - ui16Progress, 0, 0 \);

    ui8MyCharacter = ui8CharactersToDraw;

    if\( ui8CharactersToDraw > 0 \)
    \{

        glCallList\( gluiCharacter\[ 46 \] \);
        glCallList\( gluiCharacter\[ 46 \] \);
        glCallList\( gluiCharacter\[ 46 \] \);

    \}

    while\( ui8MyCharacter < ui8Characters \)
    \{

        glCallList\( gluiCharacter\[ \( uint8\_t \) cText\[ ui8MyCharacter \] \] \);

        ui8MyCharacter++;

    \}



glPopMatrix\(\);

return;

}

void RenderSingleLineTextCentred( char * cText, uint8_t ui8Characters, uint16_t ui16Width )
{

glPushMatrix\(\);

    glBindTexture\( GL\_TEXTURE\_2D, gluiFont \);

    uint8\_t ui8MyCharacter = 0;

    uint8\_t ui8EscapeWidth = \( ui8CharacterWidth\[ 46 \] \* 3 \) + 6;
    uint16\_t ui16Progress = 0;
    uint8\_t ui8CharactersToDraw = 0;

    while\( ui8MyCharacter < ui8Characters \)
    \{

        if\( \( ui16Width - ui16Progress \) - ui8CharacterWidth\[ \( uint8\_t \) cText\[ ui8MyCharacter \] \] < ui8EscapeWidth \)
        \{

            ui16Progress += ui8EscapeWidth;

            ui8MyCharacter = UINT8\_MAX;

        \}
        else
        \{

            ui16Progress += ui8CharacterWidth\[ \( uint8\_t \) cText\[ ui8MyCharacter \] \] + 2;

            ui8MyCharacter++;
            ui8CharactersToDraw++;

        \}

    \}

    glTranslatef\( floor\( \( ui16Width - ui16Progress \) / 2.0 \), 0, 0 \);

    ui8MyCharacter = 0;

    while\( ui8MyCharacter < ui8CharactersToDraw \)
    \{

        glCallList\( gluiCharacter\[ \( uint8\_t \) cText\[ ui8MyCharacter \] \] \);

        ui8MyCharacter++;

    \}

    if\( ui8MyCharacter < ui8Characters \)
    \{

        glCallList\( gluiCharacter\[ 46 \] \);
        glCallList\( gluiCharacter\[ 46 \] \);
        glCallList\( gluiCharacter\[ 46 \] \);

    \}

glPopMatrix\(\);

return;

}

void RenderSingleLineText( char * cText, uint8_t ui8Characters, uint16_t ui16Width )
{

glPushMatrix\(\);

    glBindTexture\( GL\_TEXTURE\_2D, gluiFont \);

    uint8\_t ui8MyCharacter = 0;

    uint8\_t ui8EscapeWidth = \( ui8CharacterWidth\[ 46 \] \* 3 \) + 6;
    uint16\_t ui16Progress = 0;

    while\( ui8MyCharacter < ui8Characters \)
    \{

        uint8\_t ui8Character = cText\[ ui8MyCharacter \];

        if\( \( ui16Width - ui16Progress \) - ui8CharacterWidth\[ ui8Character \] < ui8EscapeWidth \)
        \{

            glCallList\( gluiCharacter\[ 46 \] \);
            glCallList\( gluiCharacter\[ 46 \] \);
            glCallList\( gluiCharacter\[ 46 \] \);

            ui8MyCharacter = UINT8\_MAX;

        \}
        else
        \{

            glCallList\( gluiCharacter\[ ui8Character \] \);

            ui16Progress += ui8CharacterWidth\[ ui8Character \] + 2;

            ui8MyCharacter++;

        \}

    \}

glPopMatrix\(\);

return;

}

void RenderText( char * cText, uint8_t ui8Characters )
{

glPushMatrix\(\);

    glBindTexture\( GL\_TEXTURE\_2D, gluiFont \);

    uint8\_t ui8MyCharacter = 0;

    while\( ui8MyCharacter < ui8Characters \)
    \{

        glCallList\( gluiCharacter\[ \( uint8\_t \) cText\[ ui8MyCharacter \] \] \);

        ui8MyCharacter++;

    \}

glPopMatrix\(\);

return;

}

uint8_t InitFont( void )
{

GLFWimage glfwiFont;

//Try to load a font file.
if\( glfwReadImage\( "interface/font.tga", &glfwiFont, 0 \) \!= GL\_TRUE \)
\{

    printf\( "\[Font texture couldn't load\!\]" \);

    return 1;

\}

//The file should be 24-bit.  8 bits per channel RGB.
if\( glfwiFont.BytesPerPixel \!= 3 \)
\{

    glfwFreeImage\( &glfwiFont \);

    printf\( "\[Font system texture format error\!\]" \);

    return 2;

\}

//Check the image's width.
if\( glfwiFont.Width \!= 512 \)
\{

    glfwFreeImage\( &glfwiFont \);

    printf\( "\[Font system texture size error\!\]" \);

    return 3;

\}

//Check the image's height.
if\( glfwiFont.Height \!= 512 \)
\{

    glfwFreeImage\( &glfwiFont \);

    printf\( "\[Font system texture size error\!\]" \);

    return 4;

\}

//Make space for the font texture.
glGenTextures\( 1, &gluiFont \);
glBindTexture\( GL\_TEXTURE\_2D, gluiFont \);

glPixelStorei\( GL\_UNPACK\_ALIGNMENT, 1 \);

glTexParameteri\( GL\_TEXTURE\_2D, GL\_TEXTURE\_WRAP\_S, GL\_REPEAT \);
glTexParameteri\( GL\_TEXTURE\_2D, GL\_TEXTURE\_WRAP\_T, GL\_REPEAT \);
glTexParameteri\( GL\_TEXTURE\_2D, GL\_TEXTURE\_MAG\_FILTER, GL\_LINEAR \);
glTexParameteri\( GL\_TEXTURE\_2D, GL\_TEXTURE\_MIN\_FILTER, GL\_LINEAR \);

glTexEnvf\( GL\_TEXTURE\_ENV, GL\_TEXTURE\_ENV\_MODE, GL\_MODULATE \);

glTexImage2D\( GL\_TEXTURE\_2D, 0, GL\_RGB, 512, 512, 0, GL\_RGB, GL\_UNSIGNED\_BYTE, glfwiFont.Data \);

//Work out how wide each character is.
uint8\_t ui8Y = 0;

while\( ui8Y < 16 \)
\{

    uint8\_t ui8X = 0;

    while\( ui8X < 16 \)
    \{

        //This is the "base" of the character.  The upper left corner.  This actually targets the blue component.
        uint8\_t \* ui8CharacterBase = glfwiFont.Data + \( \( \( \( 15 - ui8Y \) \* 16384 \) + \( ui8X \* 32 \) \) \* 3 \);

        //Ensure we know which pixel we're actually working on...
        uint8\_t ui8XPixel = 31;
        uint8\_t ui8YPixel = 0;

        //This is our "tracking pixel" which we are going to check.
        uint8\_t \* ui8RoamingPixel = ui8CharacterBase + \( 31 \* 3 \);

        //Move right to left looking for the closest non-black pixel to the right edge.
        while\( \( ui8XPixel < UINT8\_MAX \) && \( ui8YPixel < 32 \) \)
        \{

            //If the pixel is active:
            if\( \* ui8RoamingPixel > 0 \)
            \{

                //Quit the loop
                ui8YPixel = 32;

            \}
            else
            \{

                //Move the roaming pixel down one line.
                ui8RoamingPixel += 1536;
                ui8YPixel++;

                //If the moving pixel falls off the bottom:
                if\( ui8YPixel > 31 \)
                \{

                    //Move it back up to the top and left one.
                    ui8YPixel = 0;
                    ui8XPixel--;
                    ui8RoamingPixel = ui8CharacterBase + \( ui8XPixel \* 3 \);

                \}

            \}

        \}

        ui8XPixel = 31 - ui8XPixel;

        //Ensure we know which pixel we're actually working on...
        uint8\_t ui8XPixel2 = 0;
        ui8YPixel = 0;

        //This is our "tracking pixel" which we are going to check.
        ui8RoamingPixel = ui8CharacterBase;

        //Move right to left looking for the closest non-black pixel to the right edge.
        while\( \( ui8XPixel2 < 32 \) && \( ui8YPixel < 32 \) \)
        \{

            //If the pixel is active:
            if\( \* ui8RoamingPixel > 0 \)
            \{

                //Quit the loop
                ui8YPixel = 32;

            \}
            else
            \{

                //Move the roaming pixel down one line.
                ui8RoamingPixel += 1536;
                ui8YPixel++;

                //If the moving pixel falls off the bottom:
                if\( ui8YPixel > 31 \)
                \{

                    //Move it back up to the top and left one.
                    ui8YPixel = 0;
                    ui8XPixel2++;
                    ui8RoamingPixel = ui8CharacterBase + \( ui8XPixel2 \* 3 \);

                \}

            \}

        \}

        if\( \( ui8XPixel + ui8XPixel2 \) > 31 \)
        \{

            ui8XPixel = 15;
            ui8XPixel2 = 15;

        \}

        //Generate a display list for our character.
        gluiCharacter\[ \( ui8Y \* 16 \) + ui8X \] = glGenLists\( 1 \);

        //Did it fail?
        if\( gluiCharacter\[ \( ui8Y \* 16 \) + ui8X \] == 0 \)
        \{

            //Delete all display lists.
            while\( ui8Y < UINT8\_MAX \)
            \{

                while\( ui8X < UINT8\_MAX \)
                \{

                    glDeleteLists\( gluiCharacter\[ \( ui8Y \* 16 \) + ui8X \], 1 \);

                    ui8X--;

                \}

                ui8X = 15;

                ui8Y--;

            \}

            glDeleteTextures\( 1, &gluiFont \);

            glfwFreeImage\( &glfwiFont \);

            printf\( "\[Font couldn't generate display list\!\]" \);

            return 5;

        \}

        glNewList\( gluiCharacter\[ \( ui8Y \* 16 \) + ui8X \], GL\_COMPILE \);

            glTranslatef\( -ui8XPixel2, 0, 0 \);

            glBegin\( GL\_QUADS \);

                glTexCoord2f\( ui8X / 16.0, \( 16 - ui8Y \) / 16.0 \);
                glVertex2f\( 0, 8 \);
                glTexCoord2f\( \( ui8X + 1 \) / 16.0, \( 16 - ui8Y \) / 16.0 \);
                glVertex2f\( 32, 8 \);
                glTexCoord2f\( \( ui8X + 1 \) / 16.0, \( 15 - ui8Y \) / 16.0 \);
                glVertex2f\( 32, 40 \);
                glTexCoord2f\( ui8X / 16.0, \( 15 - ui8Y \) / 16.0 \);
                glVertex2f\( 0, 40 \);

            glEnd\(\);

            glTranslatef\( \( 32 - ui8XPixel \) + 2, 0, 0 \);

        glEndList\(\);

        ui8CharacterOffsetA\[ \( ui8Y \* 16 \) + ui8X \] = ui8XPixel;
        ui8CharacterOffsetB\[ \( ui8Y \* 16 \) + ui8X \] = ui8XPixel2;
        ui8CharacterWidth\[ \( ui8Y \* 16 \) + ui8X \] = \( 32 - ui8XPixel \) - ui8XPixel2;

        ui8X++;

    \}

    ui8Y++;

\}

//Free the image struct.
glfwFreeImage\( &glfwiFont \);

return 0;

}

void TerminateFont( void )
{

uint8\_t ui8Y = 0;

while\( ui8Y < 16 \)
\{

    uint8\_t ui8X = 0;

    while\( ui8X < 16 \)
    \{

        glDeleteLists\( gluiCharacter\[ ui8X + \( ui8Y \* 16 \) \], 1 \);

        ui8X++;

    \}

    ui8Y++;

\}

glDeleteTextures\( 1, &gluiFont \);

return;

}
[/code]