roc/fonts.py
2017-05-08 13:10:48 -07:00

212 lines
No EOL
5.9 KiB
Python

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