1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
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
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
78 """@return: The height of this font"""
79 return self.font.get_height() * self.scale
80
82 """@return: The line spacing of this font"""
83 return self.font.get_linesize() * self.scale
84
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
99 if not text:
100 return
101
102 if not (text, scale) in self.stringCache:
103 currentTexture = None
104
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
117 if currentTexture is None:
118 currentTexture = g
119
120
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
141 if len(text) > 5:
142
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
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
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
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
232 try:
233 coordinates = texture.add(s)
234 except TextureAtlasFullException:
235
236 texture = self._allocateGlyphTexture()
237 return self.getGlyph(ch)
238
239 self.glyphCache[ch] = (texture, coordinates)
240 return (texture, coordinates)
241