Statistics
| Revision:

root / PyOpenGL-Demo / NeHe / lesson43 / glFreeType.py @ 1

History | View | Annotate | Download (10.7 kB)

1
#        A quick and simple opengl font library that uses GNU freetype2, written
2
#        and distributed as part of a tutorial for nehe.gamedev.net.
3
#        Sven Olsen, 2003
4
#        Translated to PyOpenGL by Brian Leair, 2004
5
# 
6
#
7

    
8

    
9

    
10
# import freetype
11
# We are going to use Python Image Library's font handling
12
# From PIL 1.1.4:
13
import ImageFont
14
from OpenGL.GL import *
15
from OpenGL.GLU import *
16

    
17

    
18
# Python 2.2 defines these directly
19
try:
20
        True
21
except NameError:
22
        True = 1==1
23
        False = 1==0
24

    
25

    
26
def is_font_available (ft, facename):
27
        """ Returns true if FreeType can find the requested face name 
28
                Pass the basname of the font e.g. "arial" or "times new roman"
29
        """
30
        if (facename in ft.available_fonts ()):
31
                return True
32
        return False
33

    
34

    
35
def next_p2 (num):
36
        """ If num isn't a power of 2, will return the next higher power of two """
37
        rval = 1
38
        while (rval<num):
39
                rval <<= 1
40
        return rval
41

    
42

    
43

    
44
def make_dlist (ft, ch, list_base, tex_base_list):
45
        """ Given an integer char code, build a GL texture into texture_array,
46
                build a GL display list for display list number display_list_base + ch.
47
                Populate the glTexture for the integer ch and construct a display
48
                list that renders the texture for ch.
49
                Note, that display_list_base and texture_base are supposed
50
                to be preallocated for 128 consecutive display lists and and 
51
                array of textures.
52
        """
53

    
54
        # //The first thing we do is get FreeType to render our character
55
        # //into a bitmap.  This actually requires a couple of FreeType commands:
56
        # //Load the Glyph for our character.
57
        # //Move the face's glyph into a Glyph object.
58
        # //Convert the glyph to a bitmap.
59
        # //This reference will make accessing the bitmap easier
60
        # - This is the 2 dimensional Numeric array
61

    
62
        # Use our helper function to get the widths of
63
        # the bitmap data that we will need in order to create
64
        # our texture.
65
        glyph = ft.getmask (chr (ch))
66
        glyph_width, glyph_height = glyph.size 
67
        # We are using PIL's wrapping for FreeType. As a result, we don't have 
68
        # direct access to glyph.advance or other attributes, so we add a 1 pixel pad.
69
        width = next_p2 (glyph_width + 1)
70
        height = next_p2 (glyph_height + 1)
71

    
72

    
73
        # python GL will accept lists of integers or strings, but not Numeric arrays
74
        # so, we buildup a string for our glyph's texture from the Numeric bitmap 
75

    
76
        # Here we fill in the data for the expanded bitmap.
77
        # Notice that we are using two channel bitmap (one for
78
        # luminocity and one for alpha), but we assign
79
        # both luminocity and alpha to the value that we
80
        # find in the FreeType bitmap. 
81
        # We use the ?: operator so that value which we use
82
        # will be 0 if we are in the padding zone, and whatever
83
        # is the the Freetype bitmap otherwise.
84
        expanded_data = ""
85
        for j in xrange (height):
86
                for i in xrange (width):
87
                        if (i >= glyph_width) or (j >= glyph_height):
88
                                value = chr (0)
89
                                expanded_data += value
90
                                expanded_data += value
91
                        else:
92
                                value = chr (glyph.getpixel ((i, j)))
93
                                expanded_data += value
94
                                expanded_data += value
95

    
96
        # -------------- Build the gl texture ------------
97

    
98
        # Now we just setup some texture paramaters.
99
        ID = glGenTextures (1)
100
        tex_base_list [ch] = ID
101
        glBindTexture (GL_TEXTURE_2D, ID)
102
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
103
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
104

    
105
        border = 0
106
        # Here we actually create the texture itself, notice
107
        # that we are using GL_LUMINANCE_ALPHA to indicate that
108
        # we are using 2 channel data.
109
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, width, height,
110
                border, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data )
111

    
112
        # With the texture created, we don't need to expanded data anymore
113
        expanded_data = None
114

    
115

    
116

    
117
        # --- Build the gl display list that draws the texture for this character ---
118

    
119
        # So now we can create the display list
120
        glNewList (list_base + ch, GL_COMPILE)
121

    
122
        if (ch == ord (" ")):
123
                glyph_advance = glyph_width
124
                glTranslatef(glyph_advance, 0, 0)
125
                glEndList()
126
        else:
127

    
128
                glBindTexture (GL_TEXTURE_2D, ID)
129

    
130
                glPushMatrix()
131

    
132
                # // first we need to move over a little so that
133
                # // the character has the right amount of space
134
                # // between it and the one before it.
135
                # glyph_left = glyph.bbox [0]
136
                # glTranslatef(glyph_left, 0, 0)
137

    
138
                # // Now we move down a little in the case that the
139
                # // bitmap extends past the bottom of the line 
140
                # // this is only true for characters like 'g' or 'y'.
141
                # glyph_descent = glyph.decent
142
                # glTranslatef(0, glyph_descent, 0)
143

    
144
                # //Now we need to account for the fact that many of
145
                # //our textures are filled with empty padding space.
146
                # //We figure what portion of the texture is used by 
147
                # //the actual character and store that information in 
148
                # //the x and y variables, then when we draw the
149
                # //quad, we will only reference the parts of the texture
150
                # //that we contain the character itself.
151
                x=float (glyph_width) / float (width)
152
                y=float (glyph_height) / float (height)
153

    
154
                # //Here we draw the texturemaped quads.
155
                # //The bitmap that we got from FreeType was not 
156
                # //oriented quite like we would like it to be,
157
                # //so we need to link the texture to the quad
158
                # //so that the result will be properly aligned.
159
                glBegin(GL_QUADS)
160
                glTexCoord2f(0,0), glVertex2f(0,glyph_height)
161
                glTexCoord2f(0,y), glVertex2f(0,0)
162
                glTexCoord2f(x,y), glVertex2f(glyph_width,0)
163
                glTexCoord2f(x,0), glVertex2f(glyph_width, glyph_height)
164
                glEnd()
165
                glPopMatrix()
166

    
167
                # Note, PIL's FreeType interface hides the advance from us.
168
                # Normal PIL clients are rendering an entire string through FreeType, not
169
                # a single character at a time like we are doing here.
170
                # Because the advance value is hidden from we will advance
171
                # the "pen" based upon the rendered glyph's width. This is imperfect.
172
                glTranslatef(glyph_width + 0.75, 0, 0)
173

    
174
                # //increment the raster position as if we were a bitmap font.
175
                # //(only needed if you want to calculate text length)
176
                # //glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL)
177

    
178
                # //Finnish the display list
179
                glEndList()
180

    
181
        return
182

    
183
# /// A fairly straight forward function that pushes
184
# /// a projection matrix that will make object world 
185
# /// coordinates identical to window coordinates.
186
def pushScreenCoordinateMatrix():
187
        glPushAttrib(GL_TRANSFORM_BIT)
188
        viewport = glGetIntegerv(GL_VIEWPORT)
189
        glMatrixMode(GL_PROJECTION)
190
        glPushMatrix()
191
        glLoadIdentity()
192
        gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3])
193
        glPopAttrib()
194
        return
195

    
196

    
197
# Pops the projection matrix without changing the current
198
# MatrixMode.
199
def pop_projection_matrix():
200
        glPushAttrib(GL_TRANSFORM_BIT)
201
        glMatrixMode(GL_PROJECTION)
202
        glPopMatrix()
203
        glPopAttrib()
204
        return
205

    
206

    
207
class font_data:
208
        def __init__ (self, facename, pixel_height):
209
                # We haven't yet allocated textures or display lists
210
                self.m_allocated = False
211
                self.m_font_height = pixel_height
212
                self.m_facename = facename
213

    
214
                # Try to obtain the FreeType font
215
                try:
216
                        ft = ImageFont.truetype (facename, pixel_height)
217
                except:
218
                        raise ValueError, "Unable to locate true type font '%s'" % (facename)
219

    
220
                # Here we ask opengl to allocate resources for
221
                # all the textures and displays lists which we
222
                # are about to create.  
223
                self.m_list_base = glGenLists (128)
224

    
225
                # Consturct a list of 128 elements. This
226
                # list will be assigned the texture IDs we create for each glyph
227
                self.textures = [None] * 128
228

    
229
                # This is where we actually create each of the fonts display lists.
230
                for i in xrange (128):
231
                        make_dlist (ft, i, self.m_list_base, self.textures);
232

    
233
                self.m_allocated = True
234

    
235

    
236
                # //We don't need the face information now that the display
237
                # //lists have been created, so we free the assosiated resources.
238
                # Note: Don't need this, as python will decref and dealloc the ft for us.
239
                ft = None
240
                return
241

    
242
        def glPrint (self, x, y, string):
243
                """
244
                # ///Much like Nehe's glPrint function, but modified to work
245
                # ///with freetype fonts.
246
                """
247
                # We want a coordinate system where things coresponding to window pixels.
248
                pushScreenCoordinateMatrix()
249
        
250
                # //We make the height about 1.5* that of
251
                h = float (self.m_font_height) / 0.63                
252
        
253
                # If There's No Text
254
                # Do Nothing
255
                if (string == None):
256
                        pop_projection_matrix()
257
                        return
258
                if (string == ""):
259
                        pop_projection_matrix()
260
                        return
261

    
262
                # //Here is some code to split the text that we have been
263
                # //given into a set of lines.  
264
                # //This could be made much neater by using
265
                # //a regular expression library such as the one avliable from
266
                # //boost.org (I've only done it out by hand to avoid complicating
267
                # //this tutorial with unnecessary library dependencies).
268
                # //Note: python string object has convenience method for this :)
269
                lines = string.split ("\n")
270

    
271
                glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT  | GL_ENABLE_BIT | GL_TRANSFORM_BIT)
272
                glMatrixMode(GL_MODELVIEW)
273
                glDisable(GL_LIGHTING)
274
                glEnable(GL_TEXTURE_2D)
275
                glDisable(GL_DEPTH_TEST)
276
                glEnable(GL_BLEND)
277
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
278

    
279
                glListBase(self.m_list_base)
280
                modelview_matrix = glGetFloatv(GL_MODELVIEW_MATRIX)
281

    
282
                # //This is where the text display actually happens.
283
                # //For each line of text we reset the modelview matrix
284
                # //so that the line's text will start in the correct position.
285
                # //Notice that we need to reset the matrix, rather than just translating
286
                # //down by h. This is because when each character is
287
                # //draw it modifies the current matrix so that the next character
288
                # //will be drawn immediatly after it.  
289
                for i in xrange (len (lines)):
290
                        line = lines [i]
291

    
292
                        glPushMatrix ()
293
                        glLoadIdentity ()
294
                        glTranslatef (x,y-h*i,0);
295
                        glMultMatrixf (modelview_matrix);
296

    
297
                        # //  The commented out raster position stuff can be useful if you need to
298
                        # //  know the length of the text that you are creating.
299
                        # //  If you decide to use it make sure to also uncomment the glBitmap command
300
                        # //  in make_dlist().
301
                        # //        glRasterPos2f(0,0);
302
                        glCallLists (line)
303
                        # //        rpos = glGetFloatv (GL_CURRENT_RASTER_POSITION)
304
                        # //        float len=x-rpos[0];
305
                        glPopMatrix()
306

    
307
                glPopAttrib()
308
                pop_projection_matrix()
309
                return
310

    
311
        def release (self):
312
                """ Release the gl resources for this Face.
313
                        (This provides the functionality of KillFont () and font_data::clean ()
314
                """
315
                if (self.m_allocated):
316
                        # Free up the glTextures and the display lists for our face
317
                        glDeleteLists ( self.m_list_base, 128);
318
                        for ID in self.textures:
319
                                glDeleteTextures (ID);
320
                        # Extra defensive. Clients that continue to try and use this object
321
                        # will now trigger exceptions.
322
                        self.list_base = None
323
                        self.m_allocated = False
324
                return
325

    
326
        def __del__ (self):
327
                """ Python destructor for when no more refs to this Face object """
328
                self.release ()
329
                return
330

    
331

    
332
# Unit Test harness if this python module is run directly.
333
if __name__ == "__main__":
334
        print "testing availability of freetype font arial\n"
335
        ft = ImageFont.truetype ("arial.ttf", 15)
336
        if ft:
337
                print "Found the TrueType font 'arial.ttf'"
338
        else:
339
                print "faild to find the TrueTYpe font 'arial'\n"