import os import pygame from OpenGL.GL import * class TextureFile(object): def __init__(self): self.id = None self.filename = None self.h = None self.w = None def load(self, filename): self.filename = filename img = pygame.image.load(filename) img.convert_alpha() glActiveTexture(GL_TEXTURE0) texid = glGenTextures(1) #print "Generated font texture id %s" % (texid,) self.id = texid imgdata = pygame.image.tostring(img, "RGBA") imgr = img.get_rect() self.h = imgr.h self.w = imgr.w dimension = GL_TEXTURE_2D glBindTexture(dimension, texid) glTexImage2D(dimension, 0, GL_RGBA8, imgr.w, imgr.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, imgdata) glTexParameteri(dimension, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameteri(dimension, GL_TEXTURE_WRAP_T, GL_CLAMP) glTexParameteri(dimension, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(dimension, GL_TEXTURE_MIN_FILTER, GL_NEAREST) class TexFont(object): def __init__(self): self.image = None self.start = None self.end = None self.rows = None self.cols = None self.charheight = None self.charwidth = None self.texwidth = None self.texheight = None self.gltex = None self.monospace = False self.charsizes = [] self.colors = (1.0, 1.0, 1.0, 1.0) @staticmethod def new(fontdir, fontname): tf = TexFont() tf.load_definition(os.path.join(fontdir, "%s.tfd" % (fontname,))) tf.load_texture(os.path.join(fontdir, "%s.png" % (fontname,))) return tf def render(self, text, color=None): if color is None: color = self.colors #print "Binding texture %s" % (self.gltex.id,) glActiveTexture(GL_TEXTURE0) dimension = GL_TEXTURE_2D glBindTexture(GL_TEXTURE_2D, self.gltex.id) glTexParameteri(dimension, GL_TEXTURE_WRAP_S, GL_CLAMP) glTexParameteri(dimension, GL_TEXTURE_WRAP_T, GL_CLAMP) glTexParameteri(dimension, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(dimension, GL_TEXTURE_MIN_FILTER, GL_NEAREST) glBegin(GL_TRIANGLES) glNormal3f(0.0, 0.0, 1.0) glColor4f(*color) #print "Rendering font" offset = 0.0 for i, letter in enumerate(text): offset = self.render_letter(letter, offset) glEnd() def render_letter(self, letter, offset): charidx = self.mapchar(letter) x1, y1, x2, y2 = self.get_coordinates(charidx) cx, cy = self.charsize(charidx) #print "Printing letter %s(%s) from %s, %s to %s, %s using texcoords %s, %s, %s, %s" % (letter, charidx, offset, 0.0, offset + cx, cy, x1*128.0, y1*128.0, x2*128.0, y2*128.0) glTexCoord2f(x1, y1); glVertex3f( offset + 0.0, 0.0, 0.0) glTexCoord2f(x1, y2); glVertex3f( offset + 0.0, cy, 0.0) glTexCoord2f(x2, y1); glVertex3f( offset + cx, 0.0, 0.0) glTexCoord2f(x2, y1); glVertex3f( offset + cx, 0.0, 0.0) glTexCoord2f(x1, y2); glVertex3f( offset + 0.0, cy, 0.0) glTexCoord2f(x2, y2); glVertex3f( offset + cx, cy, 0.0) glTexCoord2f(0.0, 0.0) return offset + cx def charsize(self, idx): if self.monospace: return (self.charwidth, self.charheight) else: return self.charsizes[idx] def mapchar(self, char): """ Maps a char to an index. Takes a character either as ascii ordinal number or as a 1-length string. Returns the index of that character in this texture font. If the character is not valid in this texture font, the index number will instead reference the last character in the font, which is usually a symbol for unknown characters (either totally blank or a box.) """ if isinstance(char, str): char = ord(char) if char >= self.start and char < self.end: return char - self.start else: return self.end - self.start - 1 def get_coordinates(self, idx): """ Maps an index to their x, y coordinates in the texture. Invalid indexes will be mapped to the last character in the font. """ if idx < 0 or idx >= (self.end - self.start): idx = (self.end - self.start - 1) xp1 = (idx % self.cols) * (self.charwidth + 1) yp1 = (idx // self.cols) * (self.charheight + 1) if self.monospace: xp2 = xp1 + self.charwidth + 1 yp2 = yp1 + self.charheight + 1 else: xp2 = xp1 + self.charsizes[idx][0] + 1 yp2 = yp1 + self.charsizes[idx][1] + 1 xtc1 = (float(xp1) + 0.0) / self.texwidth ytc1 = (float(yp1) + 0.0) / self.texheight xtc2 = (float(xp2) - 1.0) / self.texwidth ytc2 = (float(yp2) - 1.0) / self.texheight return (xtc1, ytc1, xtc2, ytc2) def load_definition(self, deffile): """ Loads a texture font definition (.tfd). These simple datafiles are generated by fontmaker.py to provide character spacing information about the associated texture font. This should be called before load_texture. """ dd = open(deffile, 'r') if dd.readline() != "fontheader\n": raise ValueError('"%s" is not a font definition file' % (deffile,)) def nextline(dd): return [int(x) for x in dd.readline().rstrip().split(',')] self.start, self.end = nextline(dd) self.charwidth, self.charheight = nextline(dd) self.cols, self.rows = nextline(dd) self.texwidth, self.texheight = nextline(dd) self.charwidth -= 1 self.charheight -= 1 assert dd.readline() == "charsizes\n" self.charsizes = [None] * (self.end - self.start) monospace = True monoheight = True for i in range(0, (self.end - self.start)): x, y = nextline(dd) if x != self.charwidth: monospace = False if y != self.charheight: monoheight = False self.charsizes[i] = (x, y) "not yet capable of dealing properly with fonts of variable height" assert monoheight self.monospace = monospace def load_texture(self, texfile): """ Loads a texture image (.png) The PNG image should contain 32-bit color with alpha channel. The image will be loaded as an OpenGL texture. This function must be called after load_definition. """ tf = TextureFile() tf.load(texfile) self.gltex = tf assert self.gltex.w == self.texwidth assert self.gltex.h == self.texheight