Module Font
[hide private]
[frames] | no frames]

Source Code for Module Font

  1  ##################################################################### 
  2  # -*- coding: iso-8859-1 -*-                                        # 
  3  #                                                                   # 
  4  # Frets on Fire                                                     # 
  5  # Copyright (C) 2006 Sami Kyöstilä                                  # 
  6  #                                                                   # 
  7  # This program is free software; you can redistribute it and/or     # 
  8  # modify it under the terms of the GNU General Public License       # 
  9  # as published by the Free Software Foundation; either version 2    # 
 10  # of the License, or (at your option) any later version.            # 
 11  #                                                                   # 
 12  # This program is distributed in the hope that it will be useful,   # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of    # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     # 
 15  # GNU General Public License for more details.                      # 
 16  #                                                                   # 
 17  # You should have received a copy of the GNU General Public License # 
 18  # along with this program; if not, write to the Free Software       # 
 19  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,        # 
 20  # MA  02110-1301, USA.                                              # 
 21  ##################################################################### 
 22   
 23  import pygame 
 24  import numpy 
 25  from OpenGL.GL import * 
 26  import sys 
 27   
 28  from Texture import Texture, TextureAtlas, TextureAtlasFullException 
 29   
30 -class Font:
31 """A texture-mapped font."""
32 - def __init__(self, fileName, size, bold = False, italic = False, underline = False, outline = True, 33 scale = 1.0, reversed = False, systemFont = False):
34 pygame.font.init() 35 self.size = size 36 self.scale = scale 37 self.glyphCache = {} 38 self.glyphSizeCache = {} 39 self.outline = outline 40 self.glyphTextures = [] 41 self.reversed = reversed 42 self.stringCache = {} 43 self.stringCacheLimit = 256 44 # Try loading a system font first if one was requested 45 self.font = None 46 if systemFont and sys.platform != "win32": 47 try: 48 self.font = pygame.font.SysFont(None, size) 49 except: 50 pass 51 if not self.font: 52 self.font = pygame.font.Font(fileName, size) 53 self.font.set_bold(bold) 54 self.font.set_italic(italic) 55 self.font.set_underline(underline)
56
57 - def getStringSize(self, s, scale = 0.002):
58 """ 59 Get the dimensions of a string when rendered with this font. 60 61 @param s: String 62 @param scale: Scale factor 63 @return: (width, height) tuple 64 """ 65 w = 0 66 h = 0 67 scale *= self.scale 68 for ch in s: 69 try: 70 s = self.glyphSizeCache[ch] 71 except: 72 s = self.glyphSizeCache[ch] = self.font.size(ch) 73 w += s[0] 74 h = max(s[1], h) 75 return (w * scale, h * scale)
76
77 - def getHeight(self):
78 """@return: The height of this font""" 79 return self.font.get_height() * self.scale
80
81 - def getLineSpacing(self):
82 """@return: The line spacing of this font""" 83 return self.font.get_linesize() * self.scale
84
85 - def setCustomGlyph(self, character, texture):
86 """ 87 Replace a character with a texture. 88 89 @param character: Character to replace 90 @param texture: L{Texture} instance 91 """ 92 texture.setFilter(GL_LINEAR, GL_LINEAR) 93 texture.setRepeat(GL_CLAMP, GL_CLAMP) 94 self.glyphCache[character] = (texture, (0.0, 0.0, texture.size[0], texture.size[1])) 95 s = .75 * self.getHeight() / float(texture.pixelSize[0]) 96 self.glyphSizeCache[character] = (texture.pixelSize[0] * s, texture.pixelSize[1] * s)
97
98 - def _renderString(self, text, pos, direction, scale):
99 if not text: 100 return 101 102 if not (text, scale) in self.stringCache: 103 currentTexture = None 104 #x, y = pos[0], pos[1] 105 x, y = 0.0, 0.0 106 vertices = numpy.empty((4 * len(text), 2), numpy.float32) 107 texCoords = numpy.empty((4 * len(text), 2), numpy.float32) 108 vertexCount = 0 109 cacheEntry = [] 110 111 for i, ch in enumerate(text): 112 g, coordinates = self.getGlyph(ch) 113 w, h = self.getStringSize(ch, scale = scale) 114 tx1, ty1, tx2, ty2 = coordinates 115 116 # Set the initial texture 117 if currentTexture is None: 118 currentTexture = g 119 120 # If the texture changed, flush the geometry 121 if currentTexture != g: 122 cacheEntry.append((currentTexture, vertexCount, numpy.array(vertices[:vertexCount]), numpy.array(texCoords[:vertexCount]))) 123 currentTexture = g 124 vertexCount = 0 125 126 vertices[vertexCount + 0] = (x, y) 127 vertices[vertexCount + 1] = (x + w, y) 128 vertices[vertexCount + 2] = (x + w, y + h) 129 vertices[vertexCount + 3] = (x, y + h) 130 texCoords[vertexCount + 0] = (tx1, ty2) 131 texCoords[vertexCount + 1] = (tx2, ty2) 132 texCoords[vertexCount + 2] = (tx2, ty1) 133 texCoords[vertexCount + 3] = (tx1, ty1) 134 vertexCount += 4 135 136 x += w * direction[0] 137 y += w * direction[1] 138 cacheEntry.append((currentTexture, vertexCount, vertices[:vertexCount], texCoords[:vertexCount])) 139 140 # Don't store very short strings 141 if len(text) > 5: 142 # Limit the cache size 143 if len(self.stringCache) > self.stringCacheLimit: 144 del self.stringCache[self.stringCache.keys()[0]] 145 self.stringCache[(text, scale)] = cacheEntry 146 else: 147 cacheEntry = self.stringCache[(text, scale)] 148 149 glPushMatrix() 150 glTranslatef(pos[0], pos[1], 0) 151 for texture, vertexCount, vertices, texCoords in cacheEntry: 152 texture.bind() 153 glVertexPointer(2, GL_FLOAT, 0, vertices) 154 glTexCoordPointer(2, GL_FLOAT, 0, texCoords) 155 glDrawArrays(GL_QUADS, 0, vertexCount) 156 glPopMatrix()
157
158 - def render(self, text, pos = (0, 0), direction = (1, 0), scale = 0.002):
159 """ 160 Draw some text. 161 162 @param text: Text to draw 163 @param pos: Text coordinate tuple (x, y) 164 @param direction: Text direction vector (x, y, z) 165 @param scale: Scale factor 166 """ 167 glEnable(GL_TEXTURE_2D) 168 glEnableClientState(GL_VERTEX_ARRAY) 169 glEnableClientState(GL_TEXTURE_COORD_ARRAY) 170 171 scale *= self.scale 172 173 if self.reversed: 174 text = "".join(reversed(text)) 175 176 if self.outline: 177 glPushAttrib(GL_CURRENT_BIT) 178 glColor4f(0, 0, 0, glGetFloatv(GL_CURRENT_COLOR)[3]) 179 self._renderString(text, (pos[0] + 0.003, pos[1] + 0.003), direction, scale) 180 glPopAttrib() 181 182 self._renderString(text, pos, direction, scale) 183 184 glDisableClientState(GL_VERTEX_ARRAY) 185 glDisableClientState(GL_TEXTURE_COORD_ARRAY) 186 glDisable(GL_TEXTURE_2D)
187
188 - def _allocateGlyphTexture(self):
189 t = TextureAtlas(size = glGetInteger(GL_MAX_TEXTURE_SIZE)) 190 t.texture.setFilter(GL_LINEAR, GL_LINEAR) 191 t.texture.setRepeat(GL_CLAMP, GL_CLAMP) 192 self.glyphTextures.append(t) 193 return t
194
195 - def getGlyph(self, ch):
196 """ 197 Get a (L{Texture}, coordinate tuple) pair for a given character. 198 199 @param ch: Character 200 @return: (L{Texture} instance, coordinate tuple) 201 """ 202 try: 203 return self.glyphCache[ch] 204 except KeyError: 205 s = self.font.render(ch, True, (255, 255, 255)) 206 207 # Draw outlines 208 """ 209 import Image, ImageFilter 210 srcImg = Image.fromstring("RGBA", s.get_size(), pygame.image.tostring(s, "RGBA")) 211 img = Image.fromstring("RGBA", s.get_size(), pygame.image.tostring(s, "RGBA")) 212 for y in xrange(img.size[1]): 213 for x in xrange(img.size[0]): 214 a = 0 215 ns = 3 216 n = 0 217 for ny in range(max(0, y - ns), min(img.size[1], y + ns)): 218 for nx in range(max(0, x - ns), min(img.size[0], x + ns)): 219 a += srcImg.getpixel((nx, ny))[3] 220 n += 1 221 if a and srcImg.getpixel((x, y))[3] == 0: 222 img.putpixel((x, y), (0, 0, 0, a / n)) 223 s = pygame.image.fromstring(img.tostring(), s.get_size(), "RGBA") 224 """ 225 226 if not self.glyphTextures: 227 texture = self._allocateGlyphTexture() 228 else: 229 texture = self.glyphTextures[-1] 230 231 # Insert the texture into the glyph cache 232 try: 233 coordinates = texture.add(s) 234 except TextureAtlasFullException: 235 # Try again with a fresh atlas 236 texture = self._allocateGlyphTexture() 237 return self.getGlyph(ch) 238 239 self.glyphCache[ch] = (texture, coordinates) 240 return (texture, coordinates)
241