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:
Here's the bitmap which was used to texture the polygons:
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: