Texture mapped text


Texture mapped fonts are described by Mark Kilgard in a paper called "A Simple OpenGL-based API for Texture Mapped Text". The texture consists of a bitmap containing the glyphs of a font and some data describing where each character is located. A string of text is created by defining a polygon for each character and texture mapping the polygon. The following illustrations show the polygons and the text rendered on them:

polygons text

Here's the bitmap which was used to texture the polygons:

font

To produce a texture mapped font, you need a bitmap of the font and a description of where in the bitmap the glyphs are. The glyph metrics are stored in a data structure in a file with the image data. In the samples on this page, the glyph data is stored in an array which looks like this:
Type glxGLYPHDATA
    'glyph metrics, in pixels
    A As Long 'distance to add to the current position before drawing the character glyph
    B As Long 'width of the drawn portion of the character glyph
    gAdvance As Long 'The total width of a character to use when drawing
    'tex coords of the glyph
    TexLeft As Single
    TexTop As Single
    TexRight As Single
    TexBottom As Single
End Type

Each font file begins with a header which contains the array:
Type glxTEXFONTDATA
    Name As String
    NumGlyphs As Long
    'size in pixels
    BmpHeight As Long
    BmpWidth As Long
    BmpFontHeight As Long 'total height of a font character
    'size in tex coords
    Glyph(1 To 94) As glxGLYPHDATA
End Type

The header is followed immediately by the image data in GL_LUMINANCE_ALPHA format.

Producing font textures

To produce a font texture you simply create a font, query each character's width, and print the characters on a bitmap. Character widths of True Type fonts are given by 3 values as shown below:



The value B is used to print to the bitmap, the value Advance is used when displaying the character. In the sample utility 'Font Texture Generator', each character is queried and its metrics stored in a glxGLYPHDATA structure. The texture coordinates are calculated at the same time, and then the character is printed to the bitmap. The utility app produces 'ftx' files - an ftx file is produced by writing out the character data, followed by the image data.

The following routine produces the font bitmap:

'---------------------------------------------------------
'create a font and draw it on the picturebox
'---------------------------------------------------------
Public Sub ShowFont(Fontname$, Pic As PictureBox)
Dim lf As LOGFONT, tm As TEXTMETRIC
Dim temp() As Byte
Dim i&, r&, rc As RECT
Dim x&, y&, cnt&, totalw&, w&, charw&, inset&
Dim OldhFont&, IsTT As Boolean
    If Len(Fontname) = 0 Then Exit Sub
    Pic.Cls
    If hFont Then
        DeleteObject hFont
    End If
    'convert points to logical units
    lf.lfHeight = -MulDiv(m_FontHeight, GetDeviceCaps(Pic.hdc, LOGPIXELSY), 72)
    If m_FontWidth <> 0 Then
        lf.lfWidth = -MulDiv(m_FontWidth, GetDeviceCaps(Pic.hdc, LOGPIXELSX), 72)
    End If
    lf.lfEscapement = m_Escapement
    lf.lfOrientation = m_Escapement
    lf.lfWeight = m_Escapement
    If m_Italic Then lf.lfItalic = 1
    If m_Underline Then lf.lfUnderline = 1
    lf.lfOutPrecision = OUT_DEFAULT_PRECIS
    lf.lfClipPrecision = OUT_DEFAULT_PRECIS
    lf.lfQuality = PROOF_QUALITY 'DEFAULT_QUALITY
    lf.lfPitchAndFamily = DEFAULT_PITCH Or FF_DONTCARE
    lf.lfCharSet = DEFAULT_CHARSET
    'convert name to byte array
    m_FontData.Name = Fontname$
    temp = StrConv(Fontname$ & Chr$(0), vbFromUnicode)
    For i = 0 To UBound(temp)
        lf.lfFaceName(i) = temp(i)
    Next
    hFont = CreateFontIndirect(lf)
    If hFont = 0 Then Exit Sub
    OldhFont = SelectObject(Pic.hdc, hFont)
    'get char height
    r = GetTextMetrics(Pic.hdc, tm) ' CurFont.tm)
    'get char width data
    If (tm.tmPitchAndFamily And TMPF_TRUETYPE) <> 0 Then
        ReDim zABC(1 To 95)
        IsTT = True
        r = GetCharABCWidths(Pic.hdc, 32, 126, zABC(1))
    Else
        'Debug.Assert 0
        Beep
        ReDim zW(1 To 95)
        r = GetCharWidth(Pic.hdc, 32, 126, zW(1))
    End If
    If r = 0 Then
        Debug.Assert 0
        Exit Sub
    End If
    'GoTo done
    'save all this
    m_FontData.BmpHeight = Pic.ScaleHeight 'height of image in pixels
    m_FontData.BmpWidth = Pic.ScaleWidth 'width of image in pels
    m_FontData.BmpFontHeight = tm.tmHeight 'height of a char in pels
    'm_FontData.gHeight = tm.tmHeight / m_FontData.BmpHeight 'texcoord height of a char
    'texh = tm.tmHeight / m_FontData.BmpHeight 'texcoord height of a char
    totalw = 1 'one pix border on left and top
    y = 1
    Do While y < m_FontData.BmpHeight + tm.tmHeight
    Do While x < m_FontData.BmpWidth
        'get size of next char
        cnt = cnt + 1
        If cnt > 94 Then GoTo done
        If IsTT Then
            m_FontData.Glyph(cnt).A = zABC(cnt).abcA
            m_FontData.Glyph(cnt).B = zABC(cnt).abcB
            'm_FontData.Glyph(cnt).C = zABC(cnt).abcC
            '
            charw = zABC(cnt).abcB
            If zABC(cnt).abcA > 0 Then
                charw = charw + zABC(cnt).abcA
            End If
            If zABC(cnt).abcC > 0 Then
                charw = charw + zABC(cnt).abcC
            End If
            'size of the glyph in pels
            m_FontData.Glyph(cnt).gAdvance = (zABC(cnt).abcA + zABC(cnt).abcB + zABC(cnt).abcC) 'in pels
            charw = charw + 1 'add a little border
            'when Windows draws, it will set the char back by A
            'we must move it out to compensate and prevent overlap of the glyphs
            If zABC(cnt).abcA < 0 Then
                inset = Abs(zABC(cnt).abcA)
            Else: inset = 0
            End If
        Else
            m_FontData.Glyph(cnt).A = 0
            m_FontData.Glyph(cnt).B = zW(cnt)
            'm_FontData.Glyph(cnt).C = 0
            charw = zW(cnt)
            m_FontData.Glyph(cnt).gAdvance = charw
        End If
        'see if it will fit
        If totalw + charw + inset > m_FontData.BmpWidth Then
            y = y + tm.tmHeight
            totalw = 1
            If y + tm.tmHeight > m_FontData.BmpHeight Then GoTo done
        End If
        'set the tex coords. these are a box around the glyph, the B value
        m_FontData.Glyph(cnt).TexTop = (m_FontData.BmpHeight - y) / m_FontData.BmpHeight
        m_FontData.Glyph(cnt).TexBottom = (m_FontData.BmpHeight - (y + tm.tmHeight)) / m_FontData.BmpHeight
        m_FontData.Glyph(cnt).TexLeft = totalw / m_FontData.BmpWidth
        m_FontData.Glyph(cnt).TexRight = (totalw + charw - 1) / m_FontData.BmpWidth
        '
        r& = TextOut(Pic.hdc, totalw + inset, y, Mid$(SampleText, cnt, 1), 1)
        totalw = totalw + charw + inset
    Loop
    Loop
done:
    m_FontData.NumGlyphs = cnt - 1
    r& = SelectObject(Pic.hdc, OldhFont)
    Set m_Pic = Pic
    m_Ready = False
End Sub



Font Texture Generator





This sample allows you to interactively design gl font textures. In the window on the left, select a font and use the arrows to adjust its size until it just fills the bitmap. Then click the preview button to see it rendered in OpenGL in the window on the right.

When you have a font you like, click Save to produce an ftx file. Click Open to load and view a previously saved ftx file.

The following routines show how to load and display the font texture:
'--------------------------------------------------------------------------
'load an ftx file and bind the texture
'--------------------------------------------------------------------------
Public Function LoadFTX(Filename$) As Boolean
Dim fn&, w&, h&
    fn = FreeFile
    Open Filename For Binary As fn
    Get #fn, , m_FontData
    h = m_FontData.BmpHeight
    w = m_FontData.BmpWidth
    If h <> 128 And h <> 256 And h <> 512 Then GoTo EH
    If w <> 128 And w <> 256 And w <> 512 Then GoTo EH
    m_DataSize = h * w * 2
    ReDim m_Data(0 To m_DataSize - 1)
    Get #fn, , m_Data
    Close #fn
    glBindTexture GL_TEXTURE_2D, m_TexID
    glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP
    glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP
    glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    glTexParameterf GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    glTexEnvf GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE
    'Create texture
    glTexImage2D GL_TEXTURE_2D, 0, 2, m_FontData.BmpWidth, m_FontData.BmpHeight, 0, _
            GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, m_Data(0)
    SetSizeFactor
    m_Ready = True
    LoadFTX = True
    Exit Function
EH:
    Debug.Print Err.Description
    Debug.Assert 0
    MsgBox "Error opening file:" & vbCrLf & Err.Description
    Exit Function
    Resume Next
End Function

'---------------------------------------------------------
Public Sub DrawText(Text$, Width!, Optional Height!)
Dim i&, char&, x!, y!
Dim l!, w!, h!
    If m_Ready = 0 Then Exit Sub
    If m_TexID = 0 Then Exit Sub
    glBindTexture GL_TEXTURE_2D, m_TexID
    '
    glPushAttrib amAllAttribBits
    glEnable GL_TEXTURE_2D
    glAlphaFunc GL_GEQUAL, 0.0625
    glEnable GL_ALPHA_TEST
    glEnable GL_BLEND
    glBlendFunc GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
    'glEnable GL_POLYGON_OFFSET
    'glPolygonOffset 0, -3
'    glPolygonMode faceFrontAndBack, pgmLine
    
    'Begin rendering quads
    glBegin GL_QUADS
    glNormal3f 0, 0, 1
    For i = 1 To Len(Text)
        'Get character index
        char = Asc(Mid$(Text, i, 1)) - 31
        'this will drop missing glyphs, or use caps if available
        If char > m_FontData.NumGlyphs Then
            If char + 31 >= 97 And char + 31 <= 122 Then
                char = char - 32
            End If
            If char > m_FontData.NumGlyphs Then
                char = 1
            End If
        End If 'NumGlyphs
        'draw a quad with texture coordinates
        w = m_FontData.Glyph(char).B * m_Factor
        h = m_FontData.BmpFontHeight * m_Factor
        l = x + m_FontData.Glyph(char).A * m_Factor
        If char = 1 And Width And x + w > Width Then
            y = y - h
            l = 0
            x = 0
        End If
        'bottom left
        glTexCoord2f m_FontData.Glyph(char).TexLeft, m_FontData.Glyph(char).TexBottom
        glVertex2f l, y
        'bottom right
        glTexCoord2f m_FontData.Glyph(char).TexRight, m_FontData.Glyph(char).TexBottom
        glVertex2f l + w, y
        'upper right
        glTexCoord2f m_FontData.Glyph(char).TexRight, m_FontData.Glyph(char).TexTop
        glVertex2f l + w, y + h
        'upper left
        glTexCoord2f m_FontData.Glyph(char).TexLeft, m_FontData.Glyph(char).TexTop
        glVertex2f l, y + h
    
        'Move to next character
        x = x + m_FontData.Glyph(char).gAdvance * m_Factor
    Next
    glEnd
    glPopAttrib
    
End Sub



Bug notice!

The first version of this sample failed sometimes on NT5 and Windows 98. This was due to a change in DIB API. This problem is fixed in the current files.

Download Font Texture Generator

The compiled utility requires glxCtl.ocx 1.3, the VB6 runtime dll, and the VB6 common controls dll.

FontTexExe.zip (~33K)

You can compile the source code in VB5 by removing the VB6-specific code - see ReadMe for details.

FontTexSrc.zip (~25K)





Please send suggestions, bug reports, and such to:



Home Page   |  Related Sites